Pyqt5中的QThreads:在worker类的构造函数中创建新对象可以吗?

Posted

技术标签:

【中文标题】Pyqt5中的QThreads:在worker类的构造函数中创建新对象可以吗?【英文标题】:QThreads in Pyqt5: is it ok to create new objects in the constructor of the worker class? 【发布时间】:2018-05-25 19:48:09 【问题描述】:

1。一些背景资料

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

文档描述了两种基本方法:(1) 工作对象方法和 (2) QThread 子类方法。

    > 下面的文章解释了为什么在使用第二种方法时需要小心: https://mayaposch.wordpress.com/2011/11/01/how-to-really-truly-use-qthreads-the-full-explanation/

    > 以下文章解释了为什么这两种方法各有千秋: https://woboq.com/blog/qthread-you-were-not-doing-so-wrong.html

我引用:

根据经验:

如果你真的不需要线程中的事件循环,你应该子类化。 如果您需要一个事件循环并在线程中处理信号和槽,您可能不需要子类化。

由于我确实需要在 QApplication 线程和新 QThread 之间进行某种通信(而且我相信信号槽是一种很好的通信方式),我将使用 worker-object 方法 .

 

2。来自官方文档的示例代码

在 QThreads 上的官方 Qt5 文档(请参阅 http://doc.qt.io/qt-5/qthread.html)上,您可以找到示例代码。我已努力将其翻译成 Python:(请参阅此 *** 问题以了解有关该翻译的更多详细信息:QThreads in Pyqt5: is this the correct C++ to Python translation of the official QThread docs?)

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)
        # Note: this constructor is empty now.
        # But would it be okay to instantiate new
        # objects here, and use them in doWork(..)?

    # A more general question: is it okay if
    # doWork(..) accesses variables that were
    # created in another thread (perhaps the
    # main QApplication thread)?

    @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()
        self.worker.moveToThread(self.workerThread)

        # 2. Connect all relevant signals
        # --------------------------------
        self.workerThread.finished.connect(self.worker.deleteLater)
        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)
        global app
        app.exit()


if __name__ == '__main__':
    app = QCoreApplication([])
    controller = Controller()
    controller.operate.emit("foo")
    sys.exit(app.exec_())

如您所见,Worker 类的构造函数是空的。这将我们带到下一段。

 

3。我的问题:可以在worker的构造函数中创建对象吗?

来自Mss. 的精彩文章。 Maya Posch (https://mayaposch.wordpress.com/2011/11/01/how-to-really-truly-use-qthreads-the-full-explanation/),我引用:

顺便说一句,这里要注意的一件非常重要的事情是,你永远不应该在 QObject 类的构造函数中分配堆对象(使用 new),因为这个分配是在主线程上执行的,而不是在新的 QThread 实例上,这意味着新创建的对象然后由主线程拥有,而不是 QThread 实例。这将使您的代码无法正常工作。相反,在这种情况下,在诸如 process() 之类的主函数槽中分配此类资源,因为当调用该对象时,该对象将位于新线程实例上,因此它将拥有该资源。[Maya Posch -如何真正真正地使用qthreads,完整解释]

本文是为 C++ 软件编写的。

这句话仍然适用于 Python 应用程序吗?也就是说,在Worker类的构造函数中实例化对象可以吗?

更一般地说,我可以问:如果doWork(..) 函数访问在另一个线程(可能是主 QApplication 线程)中实例化的变量,是否可以?


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

【问题讨论】:

言归正传,一个对象属于创建它的线程,在Qt的情况下,它避免了属于一个线程的对象在另一个线程中使用,所以正确的事情不是在构造函数中创建它,或者执行它然后使用 moveToThread() 将其移动到新线程。与其看语言,不如看 Qt 提供的基本概念。 另外,它提供的代码与您当前的问题无关,您在构造函数中没有看到任何对象的创建,并不表示您有问题。 如果您只依赖语言,将代码从一种语言翻译成另一种语言没有多大意义,因为像 Qt 这样的库提供了其他限制。 嗨@eyllanesc,你是对的。 Worker 类的构造函数当前为空。我在代码中添加了注释以澄清:-) 嗨@eyllanesc,您对翻译问题是正确的。请查看我的另一个 *** 问题,该问题侧重于翻译本身:***.com/questions/50531797/…。随意张贴你的评论:-) 【参考方案1】:

在这个答案中,我将对我的 cmets 的所有任务进行排序。

很多时候他把线程的概念和QThread混淆了,如果修改docs:

QThread 类提供了一种独立于平台的方式来管理线程

它不是本地线程的包装器,但在读取时它是一个处理程序。

run() 方法是线程的开始,docs 也表示:

线程的起点...

当您使用moveToThread() 时,它会更改对象及其子对象的线程亲和性,这指的是QObjects。

对于您的引用表明,如果您仅在 doWork() 中使用 QObject,则应避免在构造函数中创建它,此外这意味着它是通常不必要的类的成员。如果你这样做的解决方案是你使用moveToThread(),万一它是Worker的孩子显然没有必要。


Qt 为避免访问在另一个线程中创建的对象的这些问题,我建议使用线程安全的信号或 QMetaObject。

最后QThread的使用是非常低级的,做多线程任务Qt提供了其他技术QThreadPoolQRunnable避免了这些问题。在 c++ 中也有 QtConcurrent 但它在 PyQt 中不可用

【讨论】:

嗨@eyllanesc,您确定QThread 和通常的Python 线程之间的区别吗?如果我没记错的话,你告诉我QThread 实际上是在使用预编译的 Qt 代码,因此它不会提交给 Python GIL。这导致QThread 的使用更强大,但也更危险。但是,我发现了两个与您的说法相矛盾的链接:***.com/questions/1595649/… 和 mail-archive.com/pyqt@riverbankcomputing.com/msg16052.html @K.Mulier 那个wrapper取决于你是否严格定义它,显然QThread来处理你应该使用它们的本机线程,因此透视图是一个包装器。这些帖子的目的是让人们知道 Qt 使用本机线程并且不创建其他类型的线程。

以上是关于Pyqt5中的QThreads:在worker类的构造函数中创建新对象可以吗?的主要内容,如果未能解决你的问题,请参考以下文章

一起使用 psycopg2 和 Qthreads(或者只是 postgresql 和 qthreads)并更新 GUI

带参数传递的 QThreads

Java 符号引用 与 直接引用

如何让 QThreads 在控制台 PySide 程序中工作?

PyQt5 接收器

如何确保 QApplication 及其 QThreads 全部关闭