J'ai développé des outils de traitement par lots en tant que plugins python pour QGIS 1.8.
J'ai constaté que lorsque mes outils sont en cours d'exécution, l'interface graphique devient non réactive.
La sagesse générale est que le travail doit être effectué sur un thread de travail, avec les informations d'état / d'achèvement retransmises à l'interface graphique sous forme de signaux.
J'ai lu les documents riverains et étudié la source de doGeometry.py (une implémentation fonctionnelle de ftools ).
En utilisant ces sources, j'ai essayé de construire une implémentation simple afin d'explorer cette fonctionnalité avant d'apporter des modifications à une base de code établie.
La structure globale est une entrée dans le menu des plugins, qui ouvre une boîte de dialogue avec des boutons de démarrage et d'arrêt. Les boutons contrôlent un thread qui compte jusqu'à 100, renvoyant un signal à l'interface graphique pour chaque numéro. L'interface graphique reçoit chaque signal et envoie une chaîne contenant le numéro à la fois le journal des messages et le titre de la fenêtre.
Le code de cette implémentation est ici:
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()
Malheureusement, ce n'est pas un travail silencieux comme je l'espérais:
- Le titre de la fenêtre se met à jour "en direct" avec le compteur mais si je clique sur la boîte de dialogue, il ne répond pas.
- Le journal des messages est inactif jusqu'à la fin du compteur, puis présente tous les messages à la fois. Ces messages sont étiquetés avec un horodatage par QgsMessageLog et ces horodatages indiquent qu'ils ont été reçus "en direct" avec le compteur, c'est-à-dire qu'ils ne sont pas mis en file d'attente par le thread de travail ou la boîte de dialogue.
L'ordre des messages dans le journal (extrait suit) indique que startButtonHandler termine l'exécution avant que le thread de travail ne commence à fonctionner, c'est-à-dire que le thread se comporte comme un thread.
end: startButtonHandler Emit: starting work Emit: 0 ... Emit: 99 Emit: finshed work
Il semble que le thread de travail ne partage tout simplement aucune ressource avec le thread GUI. Il y a quelques lignes commentées à la fin de la source ci-dessus où j'ai essayé d'appeler msleep () et yieldCurrentThread (), mais aucune ne semblait aider.
Quelqu'un ayant une expérience avec cela est-il capable de détecter mon erreur? J'espère que c'est une erreur simple mais fondamentale qui est facile à corriger une fois qu'elle est identifiée.
la source
Réponses:
J'ai donc revu ce problème. J'ai recommencé à zéro et j'ai réussi, puis je suis retourné regarder le code ci-dessus et je ne peux toujours pas le corriger.
Afin de fournir un exemple de travail à toute personne recherchant ce sujet, je fournirai le code fonctionnel ici:
La structure de cet exemple est une classe ThreadManagerDialog à laquelle peut être affecté un WorkerThread (ou une sous-classe). Lorsque la méthode d'exécution de la boîte de dialogue est appelée, elle appelle à son tour la méthode doWork sur le travailleur. Le résultat est que tout code dans doWork s'exécutera dans un thread séparé, laissant l'interface graphique libre de répondre aux entrées de l'utilisateur.
Dans cet exemple, une instance de CounterThread est affectée en tant que travailleur et quelques barres de progression seront occupées pendant environ une minute.
Remarque: il est formaté de sorte qu'il est prêt à être collé dans la console python. Les trois dernières lignes devront être supprimées avant l'enregistrement dans un fichier .py.
la source
CounterThread
c'est juste un exemple de classe d'enfantWorkerThread
. Si vous créez votre propre classe enfant avec une implémentation plus significative de,doWork
alors ça devrait aller.