アプリケーションの応答性を維持できるように、ループの実行中にイベントを処理できるようにする必要があります。
さらに重要なことは、長時間実行されるタスクの場合、ループが開始されたらユーザーがループを停止する方法を提供する必要があることです。
これを行う簡単な方法の 1 つは、タイマーでループを開始し、ループの実行中に定期的にqApp.processEventsを呼び出すことです。
これを行うデモ スクリプトを次に示します。
import sys, time
from PyQt4 import QtGui, QtCore
class ProgressBar(QtGui.QWidget):
def __init__(self, parent=None, total=20):
super(ProgressBar, self).__init__(parent)
self.progressbar = QtGui.QProgressBar()
self.progressbar.setMinimum(1)
self.progressbar.setMaximum(total)
self.button = QtGui.QPushButton('Start')
self.button.clicked.connect(self.handleButton)
main_layout = QtGui.QGridLayout()
main_layout.addWidget(self.button, 0, 0)
main_layout.addWidget(self.progressbar, 0, 1)
self.setLayout(main_layout)
self.setWindowTitle('Progress')
self._active = False
def handleButton(self):
if not self._active:
self._active = True
self.button.setText('Stop')
if self.progressbar.value() == self.progressbar.maximum():
self.progressbar.reset()
QtCore.QTimer.singleShot(0, self.startLoop)
else:
self._active = False
def closeEvent(self, event):
self._active = False
def startLoop(self):
while True:
time.sleep(0.05)
value = self.progressbar.value() + 1
self.progressbar.setValue(value)
QtGui.qApp.processEvents()
if (not self._active or
value >= self.progressbar.maximum()):
break
self.button.setText('Start')
self._active = False
app = QtGui.QApplication(sys.argv)
bar = ProgressBar(total=101)
bar.show()
sys.exit(app.exec_())
アップデート
Python の C 実装 (つまり CPython) を使用していると仮定すると、この問題の解決策は、GUI と同時に実行する必要があるタスクの性質に完全に依存します。より基本的には、 Global Interpreter Lock (GIL)を持つ CPython によって決定されます。
CPython の GIL について説明するつもりはありません。代わりに、Dave Beazley によるこの素晴らしいPyCon ビデオを見ることをお勧めします。
一般に、バックグラウンド タスクと同時に GUI を実行しようとする場合、最初に尋ねる質問は次のとおりです。タスクは IO バウンドですか、それとも CPU バウンドですか?
IO バウンド (例: ローカル ファイル システムへのアクセス、インターネットからのダウンロードなど) の場合、CPython は常に I/O 操作のために GIL を解放するため、ソリューションは通常非常に簡単です。バックグラウンド タスクは単純に非同期で実行するか、ワーカー スレッドで実行できます。GUI の応答性を維持するために特別なことを行う必要はありません。
主な問題は、CPU バウンドのタスクで発生します。次の質問があります。タスクを一連の小さなステップに分割できますか?
可能な場合、解決策は、GUI スレッドに要求を定期的に送信して、保留中のイベントの現在のスタックを処理することです。上記のデモ スクリプトは、この手法の大まかな例です。通常、タスクは別のワーカー スレッドで実行され、タスクの各ステップが完了するたびに gui-update シグナルが送信されます。(注: ワーカー スレッドが GUI 関連の操作自体を試行しないようにすることが重要です)。
しかし、タスクを小さなステップに分割できない場合、通常のスレッド タイプのソリューションはどれも機能しません。スレッドが使用されているかどうかに関係なく、GUI はタスクが完了するまでフリーズします。
この最後のシナリオでは、唯一の解決策は、別のスレッドではなく別のプロセスを使用することです。つまり、マルチプロセッシングモジュールを使用します。もちろん、このソリューションは、ターゲット システムで複数の CPU コアが使用可能な場合にのみ有効です。使用する CPU コアが 1 つしかない場合は、基本的に何も解決できません (Python の別の実装に切り替えるか、まったく別の言語に切り替える以外に)。