从继承的 QThread 迁移到 Worker 模型

Posted

技术标签:

【中文标题】从继承的 QThread 迁移到 Worker 模型【英文标题】:migrating from Inherited QThread to Worker model 【发布时间】:2016-05-12 19:07:04 【问题描述】:

所以在我之前的问题中得到了很多帮助 (Interrupting QThread sleep 和PySide passing signals from QThread to a slot in another QThread) 我决定尝试从继承的QThread 模型更改为Worker 模型。我想我应该继续使用QThread 模型,因为我有那个工作,而另一个模型不是。但是我不确定为什么 Worker 模型不适合我。

我正在尝试这样做,如果我的方法存在固有的错误,请告诉我?

我有一个QtGui.QWidget,这是我的主要 GUI。我正在使用QPushButton 发出信号 我试图将代码简化为我认为问题所在的基础。我已经验证 datagramHandled Signal 被发出,但 packet_handled Slot 似乎没有被调用。

class myObject(QtCore.QObject):
    def __init__(self):
        super(myObject, self).__init__()
        self.ready=False

    @QtCore.Slot()
    def do_work(self):
        #send a packet
        self.ready=False
        while not self.ready:
            time.sleep(0.01)

    @QtCore.Slot(int)
    def packet_handled(self, errorCode):
        print "Packet received."
        self.ready = True

class myWidget(QtGui.QWidget):
    datagramHandled = QtCore.Signal(int)
    startRunThread = QtCore.Signal()
    def __init__(self,parent=None, **kwargs):
        super(myWidget, self).__init__(parent=parent)
        # Bunch of GUI setup stuff (working)
        self.myRunThread = QtCore.QThread()
    @QtCore.Slot()
    def run_command(self):
        self.myRunObj = myObject()
        self.myRunObj.moveToThread(self.myRunThread)
        self.datagramHandled.connect(self.myRunObj.packet_handled)
        self.startRunThread.connect(self.myRunObj.do_work)
        self.myRunThread.start()
        self.startRunThread.emit()

    @QtCore.Slot()
    def handle_datagram(self):
        #handle the incoming datagram
        errorCode = 0
        self.datagramHandled.emit(errorCode)

【问题讨论】:

【参考方案1】:

第一个问题是你需要将你的myObject.do_work方法连接到QThread.started

self.myRunThread.started.connect(self.myRunObj.do_work)

其次,您的 do_work 方法应该包含类似这些内容以启用事件处理(请原谅我生疏的 PyQt 和伪代码):

def do_work(self):
    while someCondition:
        #The next two lines are critical for events and queued signals
        if self.thread().eventDispatcher().hasPendingEvents():
            self.thread().eventDispatcher().processEvents(QEventLoop.AllEvents)
        if not self.meetsSomeConditionToContinueRunning():
            break
        elif self.hasWorkOfSomeKind():
            self.do_something_here()
        else:
            QThread.yieldCurrentThread()

如需了解更多信息,请查看QAbstractEventDispatcher 的文档。

这里的逻辑是,当信号从一个线程 (myWidget.datagramHandled) 发出时,它会在您的工作线程的事件循环中排队。调用 processEvents 处理任何未决事件(包括排队信号,它们实际上只是事件),为任何排队信号调用适当的槽 (myRunObj.packet_handled)。

进一步阅读:

How To Really, Truly Use QThreads; The Full Explanation Threading Basics

【讨论】:

我对第一部分感到困惑,也许我不使用run() 更有意义当我打电话给self.myRunThread.start() 时为什么要连接到self.myRunThread.run 哎呀。我打算将它连接到myRunThread.start。我更正了我的答案。如果是手动调用,则不需要信号/槽连接。 好的。此外,FWIW 我在我的问题中将run 重命名为do_work,我开始将它与QThread 中的run() 混淆(当我将它从QThread 类转换为QObject 时,我应该重命名它类 我已经更新了我的答案并删除了不必要的信号连接。【参考方案2】:

使用 Qt 分配计算/其他负载有 3 种可能的方式:

    明确地将负载施加到混凝土QThread 实例。那就是基于线程的并发。 隐式将负载放到池化的QThread 实例上。这更接近于基于任务的并发,但使用您自己的逻辑“手动”管理。 QThreadPool 类用于维护线程池。 在我们从未明确管理的自己的线程上下文中启动任务。那就是 基于任务的并发 和 QtConcurrent 使用的命名空间。我的猜测是基于任务的并发和“工作者模型”是一回事(观察你的变化)。请注意,QtConcurrent 确实为任务提供并行化并使用异常(这可能会影响您编写代码的方式),这与 Qt 的其余部分不同。

如果您使用 PyQt,您还可以利用为您希望通过 QtConcurrent for PyQt 实现的模式指定的功能。

附:我看到使用thread.sleep( interval ),这不是一个好的做法,并且进一步表明应该使用适当的技术来实现“工人模型”。

【讨论】:

【参考方案3】:

@JonHarper 提供的解决方案的替代方法是将您的while 循环替换为QTimer。因为您现在在工作进程中运行了一个事件循环,所以它可以正确处理QTimer 事件(只要您在相关线程中构造QTimer)。

这样,控制会定期返回到事件循环,以便在需要时可以运行其他插槽。

【讨论】:

以上是关于从继承的 QThread 迁移到 Worker 模型的主要内容,如果未能解决你的问题,请参考以下文章

从 Worker 暂停/恢复 Qthread

QThread - 使用 moveToThread 将类成员移动到线程

Qt/C++ 将 worker 的父级更改为 qthread 失败

通过 Signal/Slot 机制将 QString 传递给 QThread

停止线程的方法是啥(当我直接从 QThread 继承时)?

Qt - QThread(翻译帮助文档)