QGIS 1.8のPythonプラグインとしていくつかのバッチ処理ツールを開発しています。
ツールの実行中にGUIが応答しなくなることがわかりました。
一般的な知恵は、作業はワーカースレッドで行われ、ステータス/完了情報がシグナルとしてGUIに返されることです。
私は川岸のドキュメントを読んで、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()の呼び出しを試みたコメントアウトされた行がありますが、どちらも役に立たないようです。
これに関する経験のある人は私のエラーを見つけることができますか?私はそれが単純であるが根本的な間違いであり、それが特定されると簡単に修正できることを望んでいます。