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.