Pregunta

Preguntas:

  1. ¿Cuál es la mejor práctica para hacer el seguimiento de una banda de rodadura de el progreso sin bloquear la interfaz gráfica de usuario ( "No responde")?
  2. En general, ¿cuáles son las mejores prácticas para enhebrar como se aplica a GUI el desarrollo?

Cuestión de fondo:

  • Tengo una interfaz gráfica de usuario PyQt para Windows.
  • Se utiliza para procesar conjuntos de HTML documentos.
  • Se tarda de tres segundos a tres horas para procesar un conjunto de documentos.
  • Quiero ser capaz de procesar múltiples juegos al mismo tiempo.
  • No quiero que la interfaz gráfica de usuario para bloquear.
  • Busco en el módulo threading para lograrlo.
  • Soy relativamente nuevo en el roscado.
  • La interfaz gráfica de usuario tiene una barra de progreso.
  • quiero que se muestre el progreso de el hilo seleccionado.
  • mostrar los resultados de la seleccionada hilo si está terminado.
  • Estoy usando Python 2.5.

Mi idea: Haga que los hilos de emitir una QtSignal cuando se actualiza el progreso que desencadena alguna función que actualiza la barra de progreso. También señal cuando terminado de procesar lo que los resultados se pueden visualizar.

#NOTE: this is example code for my idea, you do not have
#      to read this to answer the question(s).

import threading
from PyQt4 import QtCore, QtGui
import re
import copy

class ProcessingThread(threading.Thread, QtCore.QObject):

    __pyqtSignals__ = ( "progressUpdated(str)",
                        "resultsReady(str)")

    def __init__(self, docs):
        self.docs = docs
        self.progress = 0   #int between 0 and 100
        self.results = []
        threading.Thread.__init__(self)

    def getResults(self):
        return copy.deepcopy(self.results)

    def run(self):
        num_docs = len(self.docs) - 1
        for i, doc in enumerate(self.docs):
            processed_doc = self.processDoc(doc)
            self.results.append(processed_doc)
            new_progress = int((float(i)/num_docs)*100)

            #emit signal only if progress has changed
            if self.progress != new_progress:
                self.emit(QtCore.SIGNAL("progressUpdated(str)"), self.getName())
            self.progress = new_progress
            if self.progress == 100:
                self.emit(QtCore.SIGNAL("resultsReady(str)"), self.getName())

    def processDoc(self, doc):
        ''' this is tivial for shortness sake '''
        return re.findall('<a [^>]*>.*?</a>', doc)


class GuiApp(QtGui.QMainWindow):

    def __init__(self):
        self.processing_threads = {}  #{'thread_name': Thread(processing_thread)}
        self.progress_object = {}     #{'thread_name': int(thread_progress)}
        self.results_object = {}      #{'thread_name': []}
        self.selected_thread = ''     #'thread_name'

    def processDocs(self, docs):
        #create new thread
        p_thread = ProcessingThread(docs)
        thread_name = "example_thread_name"
        p_thread.setName(thread_name)
        p_thread.start()

        #add thread to dict of threads
        self.processing_threads[thread_name] = p_thread

        #init progress_object for this thread
        self.progress_object[thread_name] = p_thread.progress  

        #connect thread signals to GuiApp functions
        QtCore.QObject.connect(p_thread, QtCore.SIGNAL('progressUpdated(str)'), self.updateProgressObject(thread_name))
        QtCore.QObject.connect(p_thread, QtCore.SIGNAL('resultsReady(str)'), self.updateResultsObject(thread_name))

    def updateProgressObject(self, thread_name):
        #update progress_object for all threads
        self.progress_object[thread_name] = self.processing_threads[thread_name].progress

        #update progress bar for selected thread
        if self.selected_thread == thread_name:
            self.setProgressBar(self.progress_object[self.selected_thread])

    def updateResultsObject(self, thread_name):
        #update results_object for thread with results
        self.results_object[thread_name] = self.processing_threads[thread_name].getResults()

        #update results widget for selected thread
        try:
            self.setResultsWidget(self.results_object[thread_name])
        except KeyError:
            self.setResultsWidget(None)
será apreciado

Cualquier comentario sobre este enfoque (por ejemplo, inconvenientes, las trampas, alabanzas, etc.).

Resolución:

Terminé usando la clase QThread y señales asociadas y ranuras para la comunicación entre hilos. Esto es principalmente porque mi programa ya utiliza Qt / PyQt4 de los objetos GUI / widgets. Esta solución también requiere un menor número de cambios en mi código existente para poner en práctica.

Aquí hay un enlace a un artículo aplicable Qt Qt que explica cómo maneja los hilos y señales, http: // www.linuxjournal.com/article/9602 . Extracto a continuación:

  

Afortunadamente, permisos de Qt   señales y ranuras para ser conectados   a través de hilos, siempre y cuando los hilos   se están ejecutando sus propios bucles de eventos.   Este es un método mucho más limpio de   la comunicación en comparación con el envío y la   la recepción de eventos, ya que evita   toda la contabilidad y el intermedio   clases QEvent derivados que se convierten   necesario en cualquier trivial   solicitud. La comunicación entre   las discusiones ahora se convierte en una cuestión de   la conexión de señales de un subproceso a   las ranuras en otro, y la mutexing   y cuestiones de hilo de seguridad de intercambio   datos entre hilos son manejados por   Qt.

     

¿Por qué es necesario ejecutar un evento   bucle dentro de cada hilo al que se   desee conectar señales? La razón   tiene que ver con la inter-hilo   mecanismo de comunicación utilizado por Qt   cuando la conexión de señales de una   hilo para la ranura de otro hilo.   Cuando se realiza una conexión de este tipo, es   se hace referencia como una conexión en cola.   Cuando las señales se emiten a través de una   en cola de conexión, la ranura se invoca   la próxima vez que el objeto de destino   se ejecuta bucle de eventos. Si la ranura   en su lugar había sido invocada directamente por una   señal de otro hilo, que ranura   ejecutaría en el mismo contexto que   el subproceso de llamada. Normalmente, esto es   no lo que quiere (y sobre todo no   lo que quiere si está utilizando una   conexión de base de datos, como la base de datos   conexión puede ser utilizado sólo por el   hilo que lo creó). la cola   conexión despacha adecuadamente el   indicar al objeto hilo y   invoca su ranura en su propio contexto por   piggy-respaldo en el sistema de eventos.   Esto es precisamente lo que queremos para   comunicación entre hilos en la que   algunos de los hilos están manejando   conexiones de bases de datos. el Qt   mecanismo de señal / ranura está en una raíz   aplicación de la inter-hilo   esquema de eventos que pasa señalado anteriormente,   pero con un mucho más limpio y   más fácil de usar interfaz.

Nota: eliben también tiene una buena respuesta, y si yo no estuviera usando PyQt4, que se ocupa de hilo de seguridad y mutexing, la solución habría sido mi elección.

¿Fue útil?

Solución

Si desea utilizar señales para indicar el progreso para el hilo principal, entonces realmente debe utilizar la clase QThread de PyQt en lugar de la clase Thread del módulo threading de Python.

Un ejemplo simple que utiliza QThread, señales y las ranuras se puede encontrar en la PyQt Wiki:

https://wiki.python.org/moin/PyQt/Threading,_Signals_and_Slots

Otros consejos

colas nativa de Python no funcionarán porque hay que bloquear el encendido consigue cola (), que los tapones hasta la interfaz de usuario.

Qt implementa esencialmente un sistema de colas en el interior para la comunicación hilo cruzado. Prueba esta llamada desde cualquier hilo para colocar una llamada a una ranura.

QtCore.QMetaObject.invokeMethod ()

Es torpe y no está bien documentado, pero debe hacer lo que quiera, incluso a partir de un hilo no Qt.

También puede utilizar la maquinaria para este evento. Ver QApplication (o QCoreApplication) por un método llamado algo así como "post".

Edit: He aquí un ejemplo más completo ...

He creado mi propia clase basado en QWidget. Tiene una ranura que acepta una cadena; La defino como esto:

@QtCore.pyqtSlot(str)
def add_text(self, text):
   ...

Más tarde, se crea una instancia de este widget en el hilo principal de GUI. Desde el hilo principal GUI o cualquier otro hilo (toco madera) Puedo llamar a:

QtCore.QMetaObject.invokeMethod(mywidget, "add_text", QtCore.Q_ARG(str,"hello world"))

Clunky, pero te lleva allí.

Dan.

Yo recomiendo usar cola en lugar de la señalización. En lo personal me parece una manera mucho más robusta y comprensible de la programación, porque es más sincrónica.

Hilos deben recibir "trabajos" de una cola, y volver a poner los resultados en otra cola. Sin embargo, un tercio de la cola puede ser utilizado por los hilos de notificaciones y mensajes, como los errores y los "informes sobre la marcha". Una vez que la estructura de su código de esta manera, se hace mucho más fácil de manejar.

De esta manera, un solo "cola de trabajos" y "cola resultado" también pueden ser utilizados por un grupo de subprocesos de trabajo, encamina toda la información de los hilos en el hilo principal interfaz gráfica de usuario.

Si su método de "processDoc" no cambia ningún otro dato (sólo se ve a algunos datos y devolverlo y no cambian las variables o propiedades de la clase padre) se puede utilizar Py_BEGIN_ALLOW_THREADS y Py_END_ALLOW_THREADS macroses ( ver aquí para más detalles ) en ella. Por lo que el documento se procesa de hilo que no se bloqueará el intérprete y la interfaz de usuario será actualizada.

Siempre va a tener este problema en Python. Google GIL "bloqueo intérprete global" para obtener más antecedentes. Hay dos formas generalmente recomendadas para evitar el problema que está experimentando: usar Twisted , o utilizar un módulo similar a la multiprocesamiento módulo introducido en 2.5.

Twisted requerirá que se aprende técnicas de programación asíncrona que puede ser confuso al principio, pero será útil si alguna vez necesita para escribir aplicaciones de red de alto rendimiento y será más beneficioso para usted en el largo plazo.

El módulo de multiprocesamiento se bifurcará un nuevo proceso y utiliza IPC para hacer que se comporte como si tuviera cierto roscado. El único inconveniente es que lo que se necesita Python 2.5 instalado, que es bastante nuevo y inst' incluido en la mayoría de distribuciones de Linux o OSX por defecto.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top