【问题标题】:How to start work in a non-main-thread QObject如何在非主线程 QObject 中开始工作
【发布时间】:2017-05-26 21:25:04
【问题描述】:

基于网络上的 Qt 文档和其他示例,我原以为以下使用 QThread.started 信号的程序会在非主线程中启动工作人员/强>。但事实并非如此,而是从 ma​​in 线程调用每个 work 插槽:

import time
import sys

from PyQt5.QtCore import QObject, QThread, pyqtSignal, pyqtSlot
from PyQt5.QtWidgets import QApplication, QPushButton, QTextEdit, QVBoxLayout, QWidget


def trap_exc_during_debug(*args):
    # when app exits, put breakpoint in next line when run in debugger, and analyse args
    pass


sys.excepthook = trap_exc_during_debug


class Checker(QObject):
    sig_step = pyqtSignal(int, str)
    sig_done = pyqtSignal(int)

    def __init__(self, id: int):
        super().__init__()
        self.__id = id

    @pyqtSlot()
    def work(self):
        thread_name = QThread.currentThread().objectName()
        thread_id = int(QThread.currentThreadId())
        print('running work #{} from thread "{}" (#{})'.format(self.__id, thread_name, thread_id))

        time.sleep(2)
        self.sig_step.emit(self.__id, 'step 1')
        time.sleep(2)
        self.sig_step.emit(self.__id, 'step 2')
        time.sleep(2)
        self.sig_done.emit(self.__id)


class MyWidget(QWidget):
    NUM_THREADS = 3

    sig_start = pyqtSignal()

    def __init__(self):
        super().__init__()

        self.setWindowTitle("Thread Example")
        form_layout = QVBoxLayout()
        self.setLayout(form_layout)
        self.resize(200, 200)

        self.push_button = QPushButton()
        self.push_button.clicked.connect(self.start_threads)
        self.push_button.setText("Start {} threads".format(self.NUM_THREADS))
        form_layout.addWidget(self.push_button)

        self.log = QTextEdit()
        form_layout.addWidget(self.log)
        # self.log.setMaximumSize(QSize(200, 80))

        self.text_edit = QTextEdit()
        form_layout.addWidget(self.text_edit)
        # self.text_edit.setMaximumSize(QSize(200, 60))

        QThread.currentThread().setObjectName('main')
        self.__threads_done = None
        self.__threads = None

    def start_threads(self):
        self.log.append('starting {} threads'.format(self.NUM_THREADS))
        self.push_button.setDisabled(True)

        self.__threads_done = 0
        self.__threads = []
        for idx in range(self.NUM_THREADS):
            checker = Checker(idx)
            thread = QThread()
            thread.setObjectName('thread_' + str(idx))
            self.__threads.append((thread, checker))  # need to store checker too otherwise will be gc'd
            checker.moveToThread(thread)
            checker.sig_step.connect(self.on_thread_step)
            checker.sig_done.connect(self.on_thread_done)

            # self.sig_start.connect(checker.work)  # method 1 works: each work() is in non-main thread
            thread.started.connect(checker.work)  # method 2 doesn't work: each work() is in main thread
            thread.start()

        self.sig_start.emit()  # this is only useful in method 1

    @pyqtSlot(int, str)
    def on_thread_step(self, thread_id, data):
        self.log.append('thread #{}: {}'.format(thread_id, data))
        self.text_edit.append('{}: {}'.format(thread_id, data))

    @pyqtSlot(int)
    def on_thread_done(self, thread_id):
        self.log.append('thread #{} done'.format(thread_id))
        self.text_edit.append('-- Thread {} DONE'.format(thread_id))
        self.__threads_done += 1
        if self.__threads_done == self.NUM_THREADS:
            self.log.append('No more threads')
            self.push_button.setEnabled(True)


if __name__ == "__main__":
    app = QApplication([])

    form = MyWidget()
    form.show()

    sys.exit(app.exec_())

如果我改用自定义信号,它可以正常工作。要查看这一点,请注释掉“方法 2”行并取消注释“方法 1”行并重复运行。

在不必创建自定义信号的情况下启动工作人员肯定会更好,有没有办法做到这一点(同时坚持在工作人员上调用moveToThread 的设计)?

注意:QThread.started 信号的文档没有多大帮助:

该信号在相关线程开始执行时发出

对我来说,这意味着started 将在非主线程中发出,因此它所连接的work 插槽将在非主线程中被调用,但显然情况并非如此。即使我的解释不正确并且信号实际上是在主线程中发出的,两种方法的连接类型默认Qt.AutoConnection,到QObject 上的插槽移动到另一个线程,因此started 信号应该 被异步传输(即通过每个检查器的 QThread 事件循环),显然不是这种情况。

【问题讨论】:

  • 抱歉,我无法重现此内容。两种方法都提供相同的输出,并且所有线程都在主线程之外运行(这是在 arch-linux 上使用 python 3.6.0、qt 5.7.1 和 pyqt 5.7)。
  • @ekhumoro 感谢您的检查。天哪,这很奇怪,它实际上只发生在我通过 PyCharm 调试器运行它时(顺便说一句,没有断点)。 Python 的调试器 (pdb) 运行良好。我想我会用 JetBrains 开一张票。

标签: python pyqt pyqt5 signals-slots qthread


【解决方案1】:

我向 jetbrains 发布了支持请求,他们立即回复了:

它是为了更好的调试而特意制作的。你可以取消勾选 设置 设置 |构建、执行、部署 | Python 调试器 > PyQt 兼容,并且会按预期启动工作人员。

太棒了!

【讨论】:

    猜你喜欢
    • 2017-07-30
    • 1970-01-01
    • 1970-01-01
    • 2021-12-07
    • 2011-04-21
    • 2013-10-09
    • 1970-01-01
    • 2015-01-15
    • 2010-12-30
    相关资源
    最近更新 更多