bugfix> python > 投稿

QThread をインスタンス化して使用する方法に関する公式ドキュメント ここにあります: http://doc.qt.io/qt-5/qthread.html

このドキュメントでは、2つの基本的なアプローチについて説明しています。(1)ワーカーオブジェクトアプローチと(2) QThread サブクラスアプローチ。
いくつかの記事で、2番目のアプローチは良くないことを読んでいるので、最初のアプローチに焦点を当てましょう。


編集:
@ekhumoroは私に次の興味深い記事を教えてくれました。https://woboq.com/blog/qthread-you-were-not-doing-so-wrong.html 。どうやら(1)と(2)の両方のアプローチにはそれぞれ独自のメリットがあります。

As a rule of thumb:

  • If you do not really need an event loop in the thread, you should subclass.
  • If you need an event loop and handle signals and slots within the thread, you may not need to subclass.
  • QApplicationスレッドと新しいQThreadの間に何らかの通信が必要なので(そして信号スロットが通信に適した方法だと思います)、私は使用しますワーカーオブジェクトアプローチ


    1. C ++のワーカーオブジェクトアプローチ

    のC ++コードをコピーして貼り付けましたワーカーオブジェクトアプローチ (公式のQt5ドキュメントから、http://doc.qt.io/qt-5/qthread.html):

    class Worker : public QObject
    {
        Q_OBJECT
    public slots:
        void doWork(const QString &parameter) {
            QString result;
            /* ... here is the expensive or blocking operation ... */
            emit resultReady(result);
        }
    signals:
        void resultReady(const QString &result);
    };
    class Controller : public QObject
    {
        Q_OBJECT
        QThread workerThread;
    public:
        Controller() {
            Worker *worker = new Worker;
            worker->moveToThread(&workerThread);
            connect(&workerThread, &QThread::finished, worker, &QObject::deleteLater);
            connect(this, &Controller::operate, worker, &Worker::doWork);
            connect(worker, &Worker::resultReady, this, &Controller::handleResults);
            workerThread.start();
        }
        ~Controller() {
            workerThread.quit();
            workerThread.wait();
        }
    public slots:
        void handleResults(const QString &);
    signals:
        void operate(const QString &);
    };
    


    2. Pythonのワーカーオブジェクトアプローチ

    与えられたC ++コードをPythonに変換する努力をしました。 Python 3.6とPyQt5がインストールされている場合は、このコードをコピーして貼り付け、マシンで実行するだけです。動作するはずです。

    import sys
    from PyQt5.QtWidgets import *
    from PyQt5.QtCore import *
    from PyQt5.QtGui import *
    class Worker(QObject):
        resultReady = pyqtSignal(str)
        def __init__(self, *args, **kwargs):
            super().__init__(*args, **kwargs)
        @pyqtSlot(str)
        def doWork(self, param):
            result = "hello world"
            print("foo bar")
            # ...here is the expensive or blocking operation... #
            self.resultReady.emit(result)
    
    class Controller(QObject):
        operate = pyqtSignal(str)
        def __init__(self, *args, **kwargs):
            super().__init__(*args, **kwargs)
            # 1. Create 'workerThread' and 'worker' objects
            # ----------------------------------------------
            self.workerThread = QThread()
            self.worker = Worker()          # <- SEE NOTE(1)
            self.worker.moveToThread(self.workerThread)
            # 2. Connect all relevant signals
            # --------------------------------
            self.workerThread.finished.connect(self.worker.deleteLater)
            self.workerThread.finished.connect(lambda: print("workerThread finished."))  # <- SEE NOTE(2)
            self.operate.connect(self.worker.doWork)
            self.worker.resultReady.connect(self.handleResults)
            # 3. Start the thread
            # --------------------
            self.workerThread.start()
        def __del__(self):
            self.workerThread.quit()
            self.workerThread.wait()
        @pyqtSlot(str)
        def handleResults(self, param):
            print(param)
            # One way to end application
            # ---------------------------
            # global app      # <- SEE
            # app.exit()      #     NOTE(3)
            # Another way to end application
            # -------------------------------
            self.workerThread.quit()   # <- SEE NOTE(4)
            self.thread().quit()
    
    if __name__ == '__main__':
        app = QCoreApplication([])
        controller = Controller()
        controller.operate.emit("foo")      # <- SEE NOTE(5)
        sys.exit(app.exec_())
    

    <サブ> 注(1):
    最初は worker を実装していました コンストラクター内のローカル変数としての変数。文字通りC ++サンプルをPythonに翻訳していましたが、この変数はC ++サンプルのローカル変数でもあります。
    @pschillのコメントでわかるように、このローカル変数はガベージコレクションされていたため、スレッドを実行できませんでした。変更を加えた後、予想される出力が得られます。

    <サブ> 注(2):
    workerThread を正確に知るためにこの行を追加しました 終了します。

    <サブ>注3):
    どうやら私はこれら2つのコードラインを追加する必要があります global app および app.exit() handleResults(..) へ スロット。 @Maticに感謝します!

    <サブ> 注(4):
    アプリケーションを終了するこのアプローチを(いくつかのドキュメントを通じて)発見しました。最初のコードラインは workerThread を終了します (イベントループを強制終了します)。 2番目のコードラインは mainThread を終了します (また、イベントループを強制終了します)。

    <サブ> 注(5):
    Windowsコンソールでコードを実行しても、何も起こりませんでした(ハングしただけです)。 @pschillのアドバイス(以下の彼のコメントを参照)で、このコードラインを追加して、 doWork() 関数が呼び出されます。


    3.私の質問
    1. 何よりもまず、C ++からPythonへの翻訳が正しいかどうかを知りたいと思います。エラーを見つけた場所を教えてください(もしあれば)。

    2. コードライン global app の追加 および app.exit() handleResults(..) へ スロットはぶら下げ問題を修正します。しかし、バックグラウンドで正確に何が起こるのでしょうか?これらのコードラインはワーカースレッドを殺していますか?またはメインのQApplicationスレッドですか?

    3. メインのQApplicationスレッドを殺さずにワーカースレッドを殺す方法はありますか?


    4.いくつかの答え

    1.まだわかりません。


    2. app.exit() と思う メインスレッドを強制終了します。これにより、ワーカースレッドが強制終了されます。デーモン タイプ。ワーカースレッドがデーモン コードライン print(threading.current_thread()) を挿入したため入力 で doWork(..) 関数。 <_DummyThread(Dummy-1, started daemon 9812)> を印刷しました。プログラムが終了すると、デーモンスレッドは自動的に強制終了されます。


    3.はい、方法を見つけました!ザ・ QThread::quit() 関数はあなたの友達です。公式ドキュメントはそれについて言っています:

    void QThread::quit()
     戻りコード0(成功)で終了するようにスレッドのイベントループに指示します。 QThread::exit(0) の呼び出しと同等。
    スレッドにイベントループがない場合、この関数は何もしません。
     <サブ>[http://doc.qt.io/qt-5/qthread.html#quit]

    だから私の関数 handleResults(..) 次のようになります。

       @pyqtSlot(str)
        def handleResults(self, param):
            print(param)
            self.workerThread.quit()  # Kill the worker thread
            self.thread().quit()      # Kill the main thread
    

    Controller(..) のコンストラクターにこの行を挿入して、ワーカースレッドの強制終了を確認しました。:

       self.workerThread.finished.connect(lambda: print("workerThread finished."))
    

    実際、期待どおりに行が印刷されます。私も同様の方法でメインスレッドのキルをチェックしようとしました:

       self.thread().finished.connect(lambda: print("mainThread finished."))
    

    残念ながら、この行は印刷されません。どうして?


    <サブ>これにより、現在のシステム設定を提供します。
    >Qt5( QT_VERSION_STR = 5.10.1)
    >PyQt5( PYQT_VERSION_STR = 5.10.1)
    >Python 3.6.3
    >Windows 10、64ビット

    回答 1 件
    • Pythonのサンプルアプリケーションは、何らかの方法で終了する必要があります。そうでない場合は、 Controller の後にそのまま存在します。  オブジェクトが初期化されました。

      最も簡単なことは、 handleResults を変更することです  あなたの例の機能:

      @pyqtSlot(str)
      def handleResults(self, param):
          print(param)
          global app
          app.exit()
      
      

      それが役に立てば幸い。

    あなたの答え