Estou desenvolvendo algumas ferramentas de processamento em lote como plugins python para o QGIS 1.8.
Descobri que, enquanto minhas ferramentas estão em execução, a GUI fica sem resposta.
A opinião geral é de que o trabalho deve ser realizado em um encadeamento de trabalho, com as informações de status / conclusão passadas de volta à GUI como sinais.
Eu li os documentos da margem do rio e estudei a fonte doGeometry.py (uma implementação de trabalho do ftools ).
Usando essas fontes, tentei criar uma implementação simples para explorar essa funcionalidade antes de fazer alterações em uma base de código estabelecida.
A estrutura geral é uma entrada no menu de plug-ins, que abre uma caixa de diálogo com os botões iniciar e parar. Os botões controlam um segmento que conta até 100, enviando um sinal de volta à GUI para cada número. A GUI recebe cada sinal e envia uma sequência contendo o número do log de mensagens e o título da janela.
O código desta implementação está aqui:
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()
Infelizmente, não é tranquilo trabalhar como eu esperava:
- O título da janela está atualizando "ao vivo" com o contador, mas se eu clicar na caixa de diálogo, ela não responde.
- O log de mensagens fica inativo até o contador terminar e, em seguida, apresenta todas as mensagens de uma só vez. Essas mensagens são marcadas com um registro de data e hora por QgsMessageLog e indicam que foram recebidas "ativas" com o contador, ou seja, não estão sendo enfileiradas nem pelo thread de trabalho nem pela caixa de diálogo.
A ordem das mensagens no log (executada a seguir) indica que startButtonHandler conclui a execução antes que o encadeamento do trabalhador comece a funcionar, ou seja, o encadeamento se comporta como um encadeamento.
end: startButtonHandler Emit: starting work Emit: 0 ... Emit: 99 Emit: finshed work
Parece que o thread de trabalho simplesmente não está compartilhando nenhum recurso com o thread da GUI. Existem algumas linhas comentadas no final da fonte acima, onde tentei chamar msleep () e yieldCurrentThread (), mas nenhuma delas pareceu ajudar.
Alguém com alguma experiência com isso pode detectar meu erro? Espero que seja um erro simples, mas fundamental, que seja fácil de corrigir uma vez identificado.
fonte
Respostas:
Então, eu dei uma outra olhada nesse problema. Comecei do zero e tive sucesso, depois voltei a olhar o código acima e ainda não consigo corrigi-lo.
No interesse de fornecer um exemplo de trabalho para quem pesquisa este assunto, fornecerei o código funcional aqui:
A estrutura deste exemplo é uma classe ThreadManagerDialog que pode ser atribuída a um WorkerThread (ou subclasse). Quando o método de execução da caixa de diálogo é chamado, por sua vez, chama o método doWork no trabalhador. O resultado é que qualquer código no doWork será executado em um encadeamento separado, deixando a GUI livre para responder à entrada do usuário.
Nesta amostra, uma instância do CounterThread é atribuída como o trabalhador e algumas barras de progresso serão mantidas ocupadas por um minuto ou mais.
Nota: está formatado para estar pronto para colar no console python. As últimas três linhas precisarão ser removidas antes de salvar em um arquivo .py.
fonte
CounterThread
é apenas um exemplo básico de classe infantilWorkerThread
. Se você criar sua própria classe filho com uma implementação mais significativadoWork
, deverá ficar bem.