Ich habe einige Stapelverarbeitungswerkzeuge als Python-Plugins für QGIS 1.8 entwickelt.
Ich habe festgestellt, dass die GUI während der Ausführung meiner Tools nicht mehr reagiert.
Die allgemeine Weisheit ist, dass die Arbeit an einem Arbeitsthread ausgeführt werden sollte, wobei die Status- / Abschlussinformationen als Signale an die GUI zurückgegeben werden sollten.
Ich habe die Dokumente zum Flussufer gelesen und die Quelle von doGeometry.py (eine funktionierende Implementierung von ftools ) untersucht.
Mit diesen Quellen habe ich versucht, eine einfache Implementierung zu erstellen, um diese Funktionalität zu untersuchen, bevor Änderungen an einer etablierten Codebasis vorgenommen werden.
Die Gesamtstruktur ist ein Eintrag im Plugins-Menü, der einen Dialog mit Start- und Stopp-Schaltflächen führt. Die Schaltflächen steuern einen Thread, der bis 100 zählt, und senden für jede Nummer ein Signal an die GUI zurück. Die GUI empfängt jedes Signal und sendet eine Zeichenfolge, die die Nummer sowohl des Nachrichtenprotokolls als auch den Fenstertitel enthält.
Der Code dieser Implementierung ist hier:
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()
Leider funktioniert es nicht so ruhig, wie ich es mir erhofft hatte:
- Der Fenstertitel aktualisiert "live" mit dem Zähler, aber wenn ich auf den Dialog klicke, reagiert er nicht.
- Das Nachrichtenprotokoll ist inaktiv, bis der Zähler endet, und zeigt dann alle Nachrichten gleichzeitig an. Diese Nachrichten werden von QgsMessageLog mit einem Zeitstempel versehen. Diese Zeitstempel zeigen an, dass sie "live" mit dem Zähler empfangen wurden, dh sie werden weder vom Arbeitsthread noch vom Dialog in die Warteschlange gestellt.
Die Reihenfolge der Nachrichten im Protokoll (Auszug folgt) gibt an, dass startButtonHandler die Ausführung abschließt, bevor die Kommentare des Arbeitsthreads funktionieren, dh der Thread verhält sich wie ein Thread.
end: startButtonHandler Emit: starting work Emit: 0 ... Emit: 99 Emit: finshed work
Es scheint, dass der Worker-Thread keine Ressourcen mit dem GUI-Thread teilt. Es gibt ein paar auskommentierte Zeilen am Ende der obigen Quelle, in denen ich versucht habe, msleep () undieldCurrentThread () aufzurufen, aber keine schien zu helfen.
Kann jemand, der Erfahrung damit hat, meinen Fehler erkennen? Ich hoffe, es ist ein einfacher, aber grundlegender Fehler, der leicht zu korrigieren ist, sobald er identifiziert ist.
quelle
Antworten:
Also habe ich mir dieses Problem noch einmal angesehen. Ich habe bei Null angefangen und hatte Erfolg, dann habe ich mir den obigen Code noch einmal angesehen und kann ihn immer noch nicht reparieren.
Um jedem, der sich mit diesem Thema befasst, ein funktionierendes Beispiel zu geben, werde ich hier Funktionscode bereitstellen:
Die Struktur dieses Beispiels ist eine ThreadManagerDialog-Klasse, der dann ein WorkerThread (oder eine Unterklasse) zugewiesen werden kann. Wenn die Ausführungsmethode des Dialogfelds aufgerufen wird, wird wiederum die doWork-Methode für den Worker aufgerufen. Das Ergebnis ist, dass jeder Code in doWork in einem separaten Thread ausgeführt wird und die GUI frei ist, auf Benutzereingaben zu reagieren.
In diesem Beispiel wird eine Instanz von CounterThread als Worker zugewiesen, und einige Fortschrittsbalken werden etwa eine Minute lang beschäftigt sein.
Hinweis: Dies ist so formatiert, dass es zum Einfügen in die Python-Konsole bereit ist. Die letzten drei Zeilen müssen entfernt werden, bevor sie in einer .py-Datei gespeichert werden.
quelle
CounterThread
ist nur ein Beispiel für eine KinderklasseWorkerThread
. Wenn Sie eine eigene untergeordnete Klasse mit einer aussagekräftigeren Implementierung von erstellendoWork
, sollte es Ihnen gut gehen.