Pyqt5 中的 QThreads:这是官方 QThread 文档的 C++ 到 Python 的正确翻译吗?

QThreads in Pyqt5: is this the correct C++ to Python translation of the official QThread docs?

关于如何实例化和使用 QThread 的官方文档可以在这里找到: http://doc.qt.io/qt-5/qthread.html

文档描述了两种基本方法:(1) 工作对象方法和 (2) QThread 子类方法。
我在几篇文章中读到第二种方法不好,所以让我们关注第一个。


编辑:
@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 之间的某种通信(我相信信号槽是一种很好的通信方式),我将使用 worker-object 方法.


    1。 C++ 中的工作对象方法

    我已经复制粘贴了 worker-object 方法的 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

    中的 worker-object 方法

    我努力将给定的 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 的评论中看到的那样,这个局部变量被垃圾回收了,因此我无法获取线程 运行ning。进行更改后,我得到了预期的输出。

    注 (2):
    我添加了这一行以准确知道 workerThread 何时结束。

    注 (3):
    显然我需要将这两个代码行 global appapp.exit() 添加到 handleResults(..) 插槽中。谢谢@Matic 指出这一点!

    注 (4):
    我已经(通过一些文档)发现了这种结束应用程序的方法。第一个代码行结束 workerThread(通过终止其事件循环)。第二个代码行结束 mainThread(也通过终止其事件循环)。

    注 (5):
    当 运行 在 Windows 控制台中调用代码时,什么也没有发生(只是挂起)。根据@pschill 的建议(请参阅下面他的评论),我添加了此代码行以确保调用 doWork() 函数。


    3。我的问题

    1. 首先,我想知道我从 C++ 到 Python 的翻译是否正确。请告诉我哪里出错了(如果你发现了)。

    2. 将代码行 global appapp.exit() 添加到 handleResults(..) 插槽可解决挂起问题。但背景究竟发生了什么?这些代码行是否正在杀死工作线程?还是主 QApplication 线程?

    3. 有没有办法在不杀死主 QApplication 线程的情况下杀死工作线程?


    4。一些答案

    1。还是不确定..


    2.我认为app.exit()会杀死主线程,而主线程又会杀死工作线程,因为它是deamon类型的。我发现工作线程是 deamon 类型,因为我在 doWork(..) 函数中插入了代码行 print(threading.current_thread()) 。它打印了 <_DummyThread(Dummy-1, started daemon 9812)>。当程序退出时,所有守护线程都会自动终止。


    3. 是的,我找到了方法! QThread::quit() 函数是你的朋友。官方文档是这么说的:

    void QThread::quit()
    Tells the thread's event loop to exit with return code 0 (success). Equivalent to calling QThread::exit(0).
    This function does nothing if the thread does not have an event loop.
    [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 位

    您的 Python 示例应用程序需要以某种方式退出,否则它只会在 Controller 对象初始化后停在那里。

    最简单的方法是将示例中的 handleResults 函数更改为:

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

    希望对您有所帮助。