Pyqt5 中的 QThreads:这是官方 QThread 文档的正确 C++ 到 Python 翻译吗? [英] QThreads in Pyqt5: is this the correct C++ to Python translation of the official QThread docs?

查看:80
本文介绍了Pyqt5 中的 QThreads:这是官方 QThread 文档的正确 C++ 到 Python 翻译吗?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

The official documentation on how to instantiate and use a QThread can be found here: http://doc.qt.io/qt-5/qthread.html

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

The documentation describes two basic approaches: (1) worker-object approach and (2) QThread subclass approach.
I've read in several articles that the second approach is not good, so let's focus on the first one.


@ekhumoro 向我指出了以下有趣的文章:https://woboq.com/blog/qthread-you-were-not-doing-so-wrong.html.显然,方法 (1) 和 (2) 各有各的优点:


@ekhumoro pointed me to the following interesting article: https://woboq.com/blog/qthread-you-were-not-doing-so-wrong.html . Apparently both approaches (1) and (2) each have their own merits:

作为一个经验法则:

  • 如果你真的不需要线程中的事件循环,你应该子类化.
  • 如果您需要一个事件循环并在线程内处理信号和槽,您可能不需要子类化.
  • 因为我确实需要在 QApplication 线程和新的 QThread 之间进行某种通信(我相信信号槽是一种很好的通信方式),所以我将使用 worker-object 方法.

    As I do need some sort of communication between the QApplication thread and the new QThread (and I believe signal-slot is a good way to communicate), I'll use the worker-object approach.

    我已经复制粘贴了 worker-object 方法的 C++ 代码(来自官方 Qt5 文档,请参阅 http://doc.qt.io/qt-5/qthread.html):

    I've copy-pasted the C++ code of the worker-object approach (from the official Qt5 docs, see 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 &);
    };
    

     

    我努力将给定的 C++ 代码翻译成 Python.如果您安装了 Python 3.6 和 PyQt5,您可以简单地复制粘贴此代码并在您的机器上运行它.它应该可以工作.

    I made an effort to translate the given C++ code to Python. If you have Python 3.6 and PyQt5 installed, you can simply copy-paste this code and run it on your machine. It should work.

    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 的评论中看到的,这个局部变量是垃圾收集的,因此我无法让线程运行.进行更改后,我得到了预期的输出.

    NOTE (1):
    Initially I had implemented the worker variable as a local variable in the constructor. I was literally translating the C++ sample to Python, and this variable is also a local variable in the C++ sample.
    As you can see in the comment of @pschill, this local variable was garbage collected, and therefore I couldn't get the thread running. After making the change, I get the expected output.

    <子>注意 (2):
    我添加了这一行来准确地知道 workerThread 何时完成.

    NOTE (2):
    I've added this line to know precisely when the workerThread finishes.

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

    NOTE (3):
    Apparently I need to add these two codelines global app and app.exit() to the handleResults(..) slot. Thank you @Matic to point that out!

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

    NOTE (4):
    I've discovered (through some documentations) this approach to end the application. The first codeline ends the workerThread (by killing its event-loop). The second codeline ends the mainThread (also by killing its event-loop).

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

     

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

    1. First and foremost, I would like to know if my translation from C++ to Python is correct. Please show me where I made errors (if you find any).

    将代码行 global appapp.exit() 添加到 handleResults(..) 插槽修复了挂起 -问题.但是在背景上究竟发生了什么?这些代码行会杀死工作线程吗?还是主QApplication线程?

    Adding the codelines global app and app.exit() to the handleResults(..) slot fixes the hanging-problem. But what precisely happens on the background? Are these codelines killing the worker thread? Or the main QApplication thread?

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

    Is there a way to kill the worker thread without killing the main QApplication thread?

    <小时>

    4.一些答案

    1.还是不确定..


    4. Some answers

    1. Still not sure..

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

     
    2. I believe that app.exit() kills the main thread, which in turn kills the worker thread, because it is of the deamon type. I found out that the worker thread is of deamon type because I inserted the codeline print(threading.current_thread()) in the doWork(..) function. It printed <_DummyThread(Dummy-1, started daemon 9812)>. When a program quits, any daemon threads are killed automatically.

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

     
    3. Yes, I found a way! The QThread::quit() function is your friend. The official docs say about it:

    void QThread::quit()
    告诉线程的事件循环以返回码 0(成功)退出.相当于调用 QThread::exit(0).
    如果线程没有事件循环,这个函数什么都不做.
    [http://doc.qt.io/qt-5/qthread.html#quit]

    所以我的函数 handleResults(..) 现在看起来像这样:

    So my function handleResults(..) now looks like this:

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

    我通过在 Controller(..) 的构造函数中插入这一行来检查工作线程的终止:

    I've checked the kill of the worker thread by inserting this line in the constructor of the Controller(..):

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

    我确实按预期打印了该行.我也尝试以类似的方式检查主线程的终止:

    I indeed get the line printed out as expected. I've also tried to check the kill of the main thread in a similar way:

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

    不幸的是,这行没有打印出来.为什么?

    Unfortunately this line doesn't print out. Why?

    特此提供我当前的系统设置:
        > Qt5 (QT_VERSION_STR = 5.10.1)
        > PyQt5 (PYQT_VERSION_STR = 5.10.1)
        > Python 3.6.3
        > Windows 10,64 位

    Hereby I provide my current system settings:
        >  Qt5 (QT_VERSION_STR = 5.10.1)
        >  PyQt5 (PYQT_VERSION_STR = 5.10.1)
        >  Python 3.6.3
        >  Windows 10, 64-bit

    推荐答案

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

    Your Python example application needs to somehow exit, otherwise it just sits there after the Controller object has been initialized.

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

    The easiest thing is to change the handleResults function in your example to:

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

    希望有帮助.

    这篇关于Pyqt5 中的 QThreads:这是官方 QThread 文档的正确 C++ 到 Python 翻译吗?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

    查看全文
    登录 关闭
    扫码关注1秒登录
    发送“验证码”获取 | 15天全站免登陆