visual programacion escritorio crear con argentina aplicaciones python multithreading user-interface pyqt

python - programacion - django argentina



¿Cómo hacer un seguimiento del progreso del hilo en Python sin congelar la GUI de PyQt? (5)

Siempre tendrás este problema en Python. Google GIL "bloqueo de intérprete global" para obtener más información. En general, hay dos formas recomendadas de solucionar el problema que está experimentando: use Twisted o utilice un módulo similar al módulo de multiprocesamiento que se presentó en 2.5.

Twisted requerirá que aprendas técnicas de programación asíncrona que pueden ser confusas al principio, pero serán útiles si alguna vez necesitas escribir aplicaciones de red de alto rendimiento y te serán más beneficiosas a largo plazo.

El módulo de multiprocesamiento generará un nuevo proceso y usará IPC para que se comporte como si tuviera un enhebrado verdadero. El único inconveniente es que necesitaría instalar Python 2.5, que es bastante nuevo e inst ''incluido en la mayoría de las distribuciones Linux o OSX de forma predeterminada.

Preguntas:

  1. ¿Cuál es la mejor práctica para realizar un seguimiento del progreso de una banda de rodadura sin bloquear la GUI ("No responde")?
  2. En general, ¿cuáles son las mejores prácticas para enhebrar según se aplica al desarrollo de GUI?

Antecedentes de la pregunta:

  • Tengo una GUI de PyQt para Windows.
  • Se usa para procesar conjuntos de documentos HTML.
  • Lleva de tres segundos a tres horas procesar un conjunto de documentos.
  • Quiero ser capaz de procesar múltiples conjuntos al mismo tiempo.
  • No quiero que la GUI se bloquee.
  • Estoy buscando en el módulo de subprocesamiento para lograr esto.
  • Soy relativamente nuevo en el roscado.
  • La GUI tiene una barra de progreso.
  • Quiero que muestre el progreso del hilo seleccionado.
  • Muestra los resultados del hilo seleccionado si está terminado.
  • Estoy usando Python 2.5.

Mi idea: hacer que los subprocesos emitan una QtSignal cuando se actualiza el progreso que desencadena alguna función que actualice la barra de progreso. También envíe una señal cuando finalice el procesamiento para que se puedan mostrar los resultados.

#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)

Cualquier comentario sobre este enfoque (por ejemplo, inconvenientes, trampas, elogios, etc.) será apreciado.

Resolución:

Terminé usando la clase QThread y las señales y ranuras asociadas para comunicarme entre hilos. Esto se debe principalmente a que mi programa ya usa Qt / PyQt4 para los objetos / widgets de GUI. Esta solución también requirió menos cambios en mi código existente para implementar.

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

Afortunadamente, Qt permite que las señales y las ranuras se conecten a través de subprocesos, siempre que los subprocesos ejecuten sus propios bucles de eventos. Este es un método de comunicación mucho más limpio en comparación con los eventos de envío y recepción, ya que evita todas las clases contables e intermedias derivadas de QEvent que se vuelven necesarias en cualquier aplicación no trivial. La comunicación entre hilos ahora se convierte en una cuestión de conectar señales de un hilo a las ranuras en otro, y Qt controla los problemas de muting y seguridad de hilo de intercambiar datos entre hilos.

¿Por qué es necesario ejecutar un bucle de evento dentro de cada hilo al que desea conectar las señales? La razón tiene que ver con el mecanismo de comunicación entre subprocesos utilizado por Qt cuando se conectan señales de un hilo a la ranura de otro hilo. Cuando se realiza una conexión de este tipo, se denomina conexión en cola. Cuando las señales se emiten a través de una conexión en cola, la ranura se invoca la próxima vez que se ejecuta el bucle de eventos del objeto de destino. Si, en cambio, la ranura hubiera sido invocada directamente por una señal de otro hilo, esa ranura se ejecutaría en el mismo contexto que el hilo de llamada. Normalmente, esto no es lo que desea (y especialmente no lo que desea si está utilizando una conexión de base de datos, ya que la conexión de la base de datos solo puede ser utilizada por el hilo que la creó). La conexión en cola distribuye correctamente la señal al objeto de la hebra e invoca su ranura en su propio contexto haciendo una copia de seguridad en el sistema de eventos. Esto es precisamente lo que queremos para la comunicación entre hilos en la que algunos de los hilos están manejando conexiones de bases de datos. El mecanismo de ranura / señal de Qt es en la raíz una implementación del esquema de paso de eventos entre hilos descrito anteriormente, pero con una interfaz mucho más limpia y fácil de usar.

NOTA: eliben también tiene una buena respuesta, y si no estuviera usando PyQt4, que maneja la seguridad de hilos y mutexing, su solución habría sido mi elección.


Si su método "processDoc" no cambia ningún otro dato (solo busca algunos datos y lo devuelve y no cambia las variables o propiedades de la clase padre) puede usar las macros Py_BEGIN_ALLOW_THREADS y Py_END_ALLOW_THREADS ( vea aquí para más detalles ). Por lo tanto, el documento se procesará en un hilo que no bloqueará el intérprete y se actualizará la interfaz de usuario.


Te recomiendo usar Cola en lugar de señalización. Personalmente, considero que es una forma de programación mucho más robusta y comprensible, porque es más sincrónica.

Los subprocesos deben obtener "trabajos" de una cola y volver a poner los resultados en otra cola. Sin embargo, una tercera cola puede ser utilizada por los hilos para notificaciones y mensajes, como errores e "informes de progreso". Una vez que estructure su código de esta manera, será mucho más simple de administrar.

De esta forma, una única "cola de trabajos" y "cola de resultados" también pueden ser utilizados por un grupo de subprocesos de trabajo, enruta toda la información de los subprocesos en el subproceso principal de la GUI.


Las colas nativas de python no funcionarán porque tienes que bloquear en cola get (), lo que bloquea tu UI.

Qt esencialmente implementa un sistema de colas en el interior para la comunicación de hilos cruzados. Pruebe esta llamada desde cualquier hilo para publicar una llamada a un espacio.

QtCore.QMetaObject.invokeMethod ()

Es torpe y está mal documentado, pero debería hacer lo que quiera incluso desde un hilo que no sea de Qt.

También puedes usar maquinaria de eventos para esto. Ver QApplication (o QCoreApplication) para un método llamado algo así como "publicar".

Editar: Aquí hay un ejemplo más completo ...

Creé mi propia clase basada en QWidget. Tiene una ranura que acepta una cadena; Lo defino así:

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

Más tarde, creo una instancia de este widget en el hilo principal de la GUI. Desde el hilo principal de la interfaz gráfica de usuario o cualquier otro hilo (tocar la madera) puedo llamar:

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

Clunky, pero te lleva allí.

Dan.


Si desea utilizar señales para indicar el progreso del hilo principal, entonces debería utilizar realmente 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 ranuras se puede encontrar en la Wiki de PyQt:

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