He estado desarrollando algunas herramientas de procesamiento por lotes como complementos de Python para QGIS 1.8.
He descubierto que mientras mis herramientas se están ejecutando, la GUI deja de responder.
La sabiduría general es que el trabajo debe realizarse en un subproceso de trabajo, con la información de estado / finalización devuelta a la GUI como señales.
Leí los documentos de la orilla del río y estudié la fuente de doGeometry.py (una implementación funcional de ftools ).
Usando estas fuentes, he intentado construir una implementación simple para explorar esta funcionalidad antes de hacer cambios en una base de código establecida.
La estructura general es una entrada en el menú de complementos, que abre un diálogo con los botones de inicio y parada. Los botones controlan un hilo que cuenta hasta 100, enviando una señal a la GUI para cada número. La GUI recibe cada señal y envía una cadena que contiene el número tanto el registro de mensajes como el título de la ventana.
El código de esta implementación está aquí:
from PyQt4.QtCore import *
from PyQt4.QtGui import *
from qgis.core import *
class ThreadTest:
def __init__(self, iface):
self.iface = iface
def initGui(self):
self.action = QAction( u"ThreadTest", self.iface.mainWindow())
self.action.triggered.connect(self.run)
self.iface.addPluginToMenu(u"&ThreadTest", self.action)
def unload(self):
self.iface.removePluginMenu(u"&ThreadTest",self.action)
def run(self):
BusyDialog(self.iface.mainWindow())
class BusyDialog(QDialog):
def __init__(self, parent):
QDialog.__init__(self, parent)
self.parent = parent
self.setLayout(QVBoxLayout())
self.startButton = QPushButton("Start", self)
self.startButton.clicked.connect(self.startButtonHandler)
self.layout().addWidget(self.startButton)
self.stopButton=QPushButton("Stop", self)
self.stopButton.clicked.connect(self.stopButtonHandler)
self.layout().addWidget(self.stopButton)
self.show()
def startButtonHandler(self, toggle):
self.workerThread = WorkerThread(self.parent)
QObject.connect( self.workerThread, SIGNAL( "killThread(PyQt_PyObject)" ), \
self.killThread )
QObject.connect( self.workerThread, SIGNAL( "echoText(PyQt_PyObject)" ), \
self.setText)
self.workerThread.start(QThread.LowestPriority)
QgsMessageLog.logMessage("end: startButtonHandler")
def stopButtonHandler(self, toggle):
self.killThread()
def setText(self, text):
QgsMessageLog.logMessage(str(text))
self.setWindowTitle(text)
def killThread(self):
if self.workerThread.isRunning():
self.workerThread.exit(0)
class WorkerThread(QThread):
def __init__(self, parent):
QThread.__init__(self,parent)
def run(self):
self.emit( SIGNAL( "echoText(PyQt_PyObject)" ), "Emit: starting work" )
self.doLotsOfWork()
self.emit( SIGNAL( "echoText(PyQt_PyObject)" ), "Emit: finshed work" )
self.emit( SIGNAL( "killThread(PyQt_PyObject)"), "OK")
def doLotsOfWork(self):
count=0
while count < 100:
self.emit( SIGNAL( "echoText(PyQt_PyObject)" ), "Emit: " + str(count) )
count += 1
# if self.msleep(10):
# return
# QThread.yieldCurrentThread()
Lamentablemente, no funciona en silencio como esperaba:
- El título de la ventana se actualiza "en vivo" con el contador, pero si hago clic en el cuadro de diálogo, no responde.
- El registro de mensajes está inactivo hasta que finaliza el contador, luego presenta todos los mensajes a la vez. Estos mensajes están etiquetados con una marca de tiempo por QgsMessageLog y estas marcas de tiempo indican que se recibieron "en vivo" con el contador, es decir, que no están en cola ni por el hilo de trabajo ni por el diálogo.
El orden de los mensajes en el registro (sigue el ejercicio) indica que startButtonHandler completa la ejecución antes de que el subproceso de trabajo comience a funcionar, es decir, el subproceso se comporta como un subproceso.
end: startButtonHandler Emit: starting work Emit: 0 ... Emit: 99 Emit: finshed work
Parece que el hilo de trabajo simplemente no comparte ningún recurso con el hilo de la GUI. Hay un par de líneas comentadas al final de la fuente anterior donde intenté llamar a msleep () y yieldCurrentThread (), pero ninguno pareció ayudar.
¿Alguien con alguna experiencia con esto puede detectar mi error? Espero que sea un error simple pero fundamental que sea fácil de corregir una vez que se identifica.
fuente
Respuestas:
Así que le eché otro vistazo a este problema. Comencé desde cero y tuve éxito, luego volví a mirar el código anterior y aún no puedo solucionarlo.
En aras de proporcionar un ejemplo de trabajo para cualquier persona que investigue este tema, proporcionaré un código funcional aquí:
La estructura de este ejemplo es una clase ThreadManagerDialog a la que se le puede asignar un WorkerThread (o subclase). Cuando se llama al método de ejecución del diálogo, a su vez llamará al método doWork en el trabajador. El resultado es que cualquier código en doWork se ejecutará en un hilo separado, dejando a la GUI libre para responder a la entrada del usuario.
En este ejemplo, se asigna una instancia de CounterThread como trabajador y se mantendrán ocupadas un par de barras de progreso durante un minuto más o menos.
Nota: esto está formateado para que esté listo para pegar en la consola de Python. Las últimas tres líneas deberán eliminarse antes de guardarlas en un archivo .py.
fuente
CounterThread
es solo un ejemplo básico de clase infantilWorkerThread
. Si crea su propia clase secundaria con una implementación más significativa,doWork
entonces debería estar bien.