12

次のコードを使用すると、数秒後にアプリケーションが停止します。そして屋台とは、ハングアップを意味します。待機または強制終了を示すウィンドウが Windows から表示されます。

これは、プログレス バー ウィンドウの内側をクリックした場合、またはウィンドウの外側をクリックした場合にのみ発生するため、フォーカスが失われることを付け加えておきます。例を開始して何も触れないと、正常に動作します。

from PyQt4 import QtCore
from PyQt4 import QtGui


class ProgressBar(QtGui.QWidget):
    def __init__(self, parent=None, total=20):
        super(ProgressBar, self).__init__(parent)
        self.name_line = QtGui.QLineEdit()

        self.progressbar = QtGui.QProgressBar()
        self.progressbar.setMinimum(1)
        self.progressbar.setMaximum(total)

        main_layout = QtGui.QGridLayout()
        main_layout.addWidget(self.progressbar, 0, 0)

        self.setLayout(main_layout)
        self.setWindowTitle("Progress")

    def update_progressbar(self, val):
        self.progressbar.setValue(val)   

これを次のように使用します。

app = QtGui.QApplication(sys.argv)
bar = ProgressBar(total=101)
bar.show()

for i in range(2,100):
    bar.update_progressbar(i)
    time.sleep(1)

助けてくれてありがとう。

4

2 に答える 2

13

アプリケーションの応答性を維持できるように、ループの実行中にイベントを処理できるようにする必要があります。

さらに重要なことは、長時間実行されるタスクの場合、ループが開始されたらユーザーがループを停止する方法を提供する必要があることです。

これを行う簡単な方法の 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 の別の実装に切り替えるか、まったく別の言語に切り替える以外に)。

于 2012-11-07T17:53:07.070 に答える
0

GUI プログラミングを行う場合、なんらかの形式のマルチスレッドを使用する必要があります。ワーカー スレッドと、GUI を更新してマウスとキーボードのイベントに応答するスレッドが必要です。それにはいくつかの方法があります。QProgressBarの使用方法の優れた実例があるこのQProgressBar チュートリアルをご覧になることをお勧めします。

チュートリアルでは、GUI イベントに応答できるようにメイン スレッドに制御を戻す方法である QBasicTimer を使用しています。コードでを使用time.sleepすると、実行中の唯一のスレッドを 1 秒間ブロックします。

于 2012-11-07T13:02:10.340 に答える