質問:
- GUI をロックせずに (「応答なし」) スレッドの進行状況を追跡するためのベスト プラクティスは何ですか?
- 一般に、GUI 開発に適用されるスレッド化のベスト プラクティスは何ですか?
質問の背景:
- Windows 用の PyQt GUI があります。
- HTMLドキュメントのセットを処理するために使用されます。
- 一連のドキュメントを処理するには、3 秒から 3 時間かかります。
- 複数のセットを同時に処理できるようにしたい。
- GUI をロックしたくありません。
- これを達成するためにスレッドモジュールを見ています。
- 私はスレッド化に比較的慣れていません。
- GUI には進行状況バーが 1 つあります。
- 選択したスレッドの進行状況を表示したい。
- 選択したスレッドが終了した場合、そのスレッドの結果を表示します。
- Python 2.5 を使用しています。
私の考え:進行状況が更新されたときに、進行状況バーを更新する関数をトリガーするQtSignalをスレッドに発行させます。また、結果を表示できるように、処理が終了したときに通知します。
#NOTE: this is example code for my idea, you do not have
# to read this to answer the question(s).
import threading
from PyQt4 import QtCore, QtGui
import re
import copy
class ProcessingThread(threading.Thread, QtCore.QObject):
__pyqtSignals__ = ( "progressUpdated(str)",
"resultsReady(str)")
def __init__(self, docs):
self.docs = docs
self.progress = 0 #int between 0 and 100
self.results = []
threading.Thread.__init__(self)
def getResults(self):
return copy.deepcopy(self.results)
def run(self):
num_docs = len(self.docs) - 1
for i, doc in enumerate(self.docs):
processed_doc = self.processDoc(doc)
self.results.append(processed_doc)
new_progress = int((float(i)/num_docs)*100)
#emit signal only if progress has changed
if self.progress != new_progress:
self.emit(QtCore.SIGNAL("progressUpdated(str)"), self.getName())
self.progress = new_progress
if self.progress == 100:
self.emit(QtCore.SIGNAL("resultsReady(str)"), self.getName())
def processDoc(self, doc):
''' this is tivial for shortness sake '''
return re.findall('<a [^>]*>.*?</a>', doc)
class GuiApp(QtGui.QMainWindow):
def __init__(self):
self.processing_threads = {} #{'thread_name': Thread(processing_thread)}
self.progress_object = {} #{'thread_name': int(thread_progress)}
self.results_object = {} #{'thread_name': []}
self.selected_thread = '' #'thread_name'
def processDocs(self, docs):
#create new thread
p_thread = ProcessingThread(docs)
thread_name = "example_thread_name"
p_thread.setName(thread_name)
p_thread.start()
#add thread to dict of threads
self.processing_threads[thread_name] = p_thread
#init progress_object for this thread
self.progress_object[thread_name] = p_thread.progress
#connect thread signals to GuiApp functions
QtCore.QObject.connect(p_thread, QtCore.SIGNAL('progressUpdated(str)'), self.updateProgressObject(thread_name))
QtCore.QObject.connect(p_thread, QtCore.SIGNAL('resultsReady(str)'), self.updateResultsObject(thread_name))
def updateProgressObject(self, thread_name):
#update progress_object for all threads
self.progress_object[thread_name] = self.processing_threads[thread_name].progress
#update progress bar for selected thread
if self.selected_thread == thread_name:
self.setProgressBar(self.progress_object[self.selected_thread])
def updateResultsObject(self, thread_name):
#update results_object for thread with results
self.results_object[thread_name] = self.processing_threads[thread_name].getResults()
#update results widget for selected thread
try:
self.setResultsWidget(self.results_object[thread_name])
except KeyError:
self.setResultsWidget(None)
このアプローチに関するコメント (欠点、落とし穴、賞賛など) を歓迎します。
解像度:
QThread クラスと関連するシグナルとスロットを使用して、スレッド間で通信することになりました。これは主に、私のプログラムが既に GUI オブジェクト/ウィジェットに Qt/PyQt4 を使用しているためです。このソリューションでは、既存のコードに実装する変更も少なくて済みます。
Qt がスレッドとシグナルを処理する方法を説明している該当する Qt の記事へのリンクは次のとおりです ( http://www.linuxjournal.com/article/9602 ) 。以下抜粋:
幸いなことに、Qt では、スレッドが独自のイベント ループを実行している限り、シグナルとスロットをスレッド間で接続できます。これは、重要なアプリケーションで必要になるすべてのブックキーピングおよび中間の QEvent 派生クラスを回避するため、イベントの送受信に比べてはるかにクリーンな通信方法です。スレッド間の通信は、あるスレッドから別のスレッドのスロットに信号を接続することになり、スレッド間でデータを交換する際のミューテックスとスレッド セーフの問題は Qt によって処理されます。
シグナルを接続する各スレッド内でイベント ループを実行する必要があるのはなぜですか? その理由は、あるスレッドから別のスレッドのスロットに信号を接続するときに Qt が使用するスレッド間通信メカニズムに関係しています。このような接続が確立されると、キュー接続と呼ばれます。キューに入れられた接続を介してシグナルが発行されると、宛先オブジェクトのイベントループが次に実行されるときにスロットが呼び出されます。代わりにスロットが別のスレッドからのシグナルによって直接呼び出された場合、そのスロットは呼び出しスレッドと同じコンテキストで実行されます。通常、これはあなたが望むものではありません (特に、データベース接続を使用している場合、データベース接続はそれを作成したスレッドだけが使用できるため、望ましくありません)。キューに入れられた接続は、シグナルをスレッド オブジェクトに適切にディスパッチし、イベント システムをピギーバックすることによって、独自のコンテキストでそのスロットを呼び出します。これはまさに、一部のスレッドがデータベース接続を処理するスレッド間通信に必要なものです。Qt のシグナル/スロット メカニズムは、上記で概説したスレッド間イベント パッシング スキームの実装の根底にありますが、よりクリーンで使いやすいインターフェイスを備えています。
注: elibenにも適切な回答があります。スレッド セーフとミューテックスを処理する PyQt4 を使用していなければ、彼の解決策が私の選択だったでしょう。