Ho sviluppato alcuni strumenti di elaborazione batch come plug-in Python per QGIS 1.8.
Ho scoperto che mentre i miei strumenti sono in esecuzione la GUI diventa non reattiva.
La saggezza generale è che il lavoro dovrebbe essere svolto su un thread di lavoro, con le informazioni sullo stato / completamento restituite alla GUI come segnali.
Ho letto i documenti della riva del fiume e ho studiato la fonte di doGeometry.py (un'implementazione funzionante da ftools ).
Usando queste fonti ho provato a costruire una semplice implementazione per esplorare questa funzionalità prima di apportare modifiche a una base di codice stabilita.
La struttura generale è una voce nel menu dei plug-in, che apre una finestra di dialogo con i pulsanti di avvio e arresto. I pulsanti controllano un thread che conta fino a 100, inviando un segnale alla GUI per ogni numero. La GUI riceve ogni segnale e invia una stringa contenente il numero sia del registro dei messaggi sia del titolo della finestra.
Il codice di questa implementazione è qui:
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()
Sfortunatamente non è tranquillo lavorare come speravo:
- Il titolo della finestra si aggiorna "live" con il contatore, ma se faccio clic sulla finestra di dialogo, non risponde.
- Il registro dei messaggi è inattivo fino alla fine del contatore, quindi presenta tutti i messaggi contemporaneamente. Questi messaggi sono contrassegnati con un timestamp da QgsMessageLog e questi timestamp indicano che sono stati ricevuti "in tempo reale" con il contatore, cioè non vengono accodati dal thread di lavoro o dalla finestra di dialogo.
L'ordine dei messaggi nel registro (seguito dall'esempio) indica che startButtonHandler completa l'esecuzione prima che il thread di lavoro inizi a funzionare, ovvero il thread si comporta come un thread.
end: startButtonHandler Emit: starting work Emit: 0 ... Emit: 99 Emit: finshed work
Sembra che il thread di lavoro non stia condividendo risorse con il thread della GUI. Ci sono un paio di righe commentate alla fine della fonte sopra dove ho provato a chiamare msleep () e yieldCurrentThread (), ma nessuno dei due sembrava aiutare.
Qualcuno con qualche esperienza con questo in grado di individuare il mio errore? Spero che sia un errore semplice ma fondamentale che sia facile da correggere una volta identificato.