Я разрабатывал некоторые инструменты пакетной обработки в виде плагинов Python для QGIS 1.8.
Я обнаружил, что когда мои инструменты работают, графический интерфейс перестает отвечать на запросы.
Общая мудрость заключается в том, что работа должна выполняться в рабочем потоке, а информация о состоянии / завершении передается обратно в графический интерфейс в качестве сигналов.
Я прочитал документы по берегам реки и изучил источник doGeometry.py (рабочая реализация из ftools ).
Используя эти источники, я попытался создать простую реализацию, чтобы изучить эту функциональность, прежде чем вносить изменения в установленную базу кода.
Общая структура - это запись в меню плагинов, которая запускает диалог с кнопками запуска и остановки. Кнопки управляют потоком, который считает до 100, посылая сигнал обратно в GUI для каждого номера. GUI получает каждый сигнал и отправляет строку, содержащую номер как журнала сообщений, так и заголовка окна.
Код этой реализации находится здесь:
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()
К сожалению, работать не так тихо, как я надеялся:
- Заголовок окна обновляется «вживую» со счетчиком, но если я нажимаю на диалоговое окно, оно не отвечает.
- Журнал сообщений неактивен до тех пор, пока не закончится счетчик, а затем представляет все сообщения одновременно. Эти сообщения помечаются меткой времени с помощью QgsMessageLog, и эти метки времени указывают, что они были получены «вживую» со счетчиком, т.е. они не помещаются в очередь ни рабочим потоком, ни диалогом.
Порядок сообщений в журнале (ниже следует отрывок) указывает, что startButtonHandler завершает выполнение до того, как рабочий поток начинает работу, т.е. поток ведет себя как поток.
end: startButtonHandler Emit: starting work Emit: 0 ... Emit: 99 Emit: finshed work
Кажется, рабочий поток просто не делит ресурсы с потоком GUI. В конце вышеприведенного источника есть пара закомментированных строк, где я пытался вызвать msleep () и yieldCurrentThread (), но ни одна из них не помогла.
Кто-нибудь с таким опытом может распознать мою ошибку? Я надеюсь, что это простая, но фундаментальная ошибка, которую легко исправить, если ее выявить.