从继承的 QThread 迁移到 Worker 模型

migrating from Inherited QThread to Worker model

所以在我之前的问题中得到了很多帮助 () 我决定尝试从继承的 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)

使用 Qt 分配 computation/other 负载有 3 种可能的方法:

  1. 明确地将负载放到具体的 QThread 实例上。即基于线程的并发.
  2. 隐式地将负载放入池 QThread 实例。这更接近于基于任务的并发,但 'manually' 使用您自己的逻辑进行管理。 QThreadPool class 用于维护线程池。
  3. 在我们从未明确管理的自己的线程上下文中启动任务。即基于任务的并发QtConcurrent 使用的命名空间。我的猜测是基于任务的并发和 "worker model" 是一回事(观察你的变化)。请注意 QtConcurrent 确实为任务提供并行化并使用异常(这可能会影响您编写代码的方式),这与 Qt 的其余部分不同。

鉴于您使用 PyQt,您还可以利用为您要使用 QtConcurrent for PyQt 实现的模式指定的功能。

P.S。我看到使用 thread.sleep( interval ),这不是一个好的做法,还有一个迹象表明应该使用正确的技术来实现 'Worker model'。

第一个问题是您需要将 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)。

延伸阅读:

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

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