从继承的 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 模型的主要内容,如果未能解决你的问题,请参考以下文章
QThread - 使用 moveToThread 将类成员移动到线程
Qt/C++ 将 worker 的父级更改为 qthread 失败