为啥我的 QThread 正在使主线程挨饿?
Posted
技术标签:
【中文标题】为啥我的 QThread 正在使主线程挨饿?【英文标题】:Why is the work being done by my QThread starving the main thread?为什么我的 QThread 正在使主线程挨饿? 【发布时间】:2012-08-15 15:14:51 【问题描述】:这是我基于 QThread 编写的可爱线程。您会注意到它有一个事件队列。 4 秒后,一个事件触发并在doWork
中执行一些工作。 doWork
应该在所有打印之间休眠,并让其他线程有机会运行。足以说明所有打印和睡眠 doWork
运行的时间足够长,以至于另一个线程确实应该有一些时间来执行。
from PySide.QtCore import *
from PySide.QtGui import *
class DoStuffPeriodically(QThread):
def __init__(self):
super(DoStuffPeriodically, self).__init__()
def doWork(self):
#... do work, post signal to consumer
print "Start work"
for i in range(0,100):
print "work %i" % i
QThread.msleep(10)
print "Done work"
return
def run(self):
""" Setup "pullFiles" to be called once a second"""
self.timer= QTimer()
self.timer.setSingleShot(True)
self.timer.timeout.connect(self.doWork)
self.timer.start(4000)
self.exec_()
这是我用来控制线程的*** QT 小部件。它基本上只是一个启动/停止线程的按钮。
class Widg(QWidget):
def __init__(self):
super(Widg, self).__init__()
self.thread = DoStuffPeriodically()
self.startStopButton = QPushButton()
hBoxLayout = QHBoxLayout()
hBoxLayout.addWidget(self.startStopButton)
self.startStopButton.pressed.connect(self.startStopThread)
self.setLayout(hBoxLayout)
self.threadRunning = False
def startStopThread(self):
if self.threadRunning:
print "Stopping..."
self.thread.exit(0)
self.threadRunning = False
print "Stopped"
else:
print "Starting..."
self.thread.start()
self.threadRunning = True
print "Started"
if __name__ == "__main__":
from sys import argv
qApp = QApplication(argv)
widg = Widg()
widg.show()
qApp.exec_()
如果我单击 startStopButton,我希望看到线程开始打印
开始... 开始... 开始工作 工作 0 工作 1 ... 工作 99 完成工作但我想做的是能够在线程工作时停止线程。我期待一些类似的东西
开始... 开始... 开始工作 工作 0 工作 1 ... 工作N 停止... 工作 99 完成工作 停...相反,工作线程似乎正在阻止主线程执行?而且我必须等待工作完成才能单击 startStopButton,给我
开始... 开始... 开始工作 工作 0 工作 1 ... 工作 99 完成工作 停止... 停...doWork
运行多长时间并不重要。我已经将它提高到循环 10000 次。 它似乎永远不会给主线程时间,并且小部件没有响应。我是否正在做一些阻止真正线程实际工作的事情?
(我使用的是 python 2.7 和 pyside 1.10。)
更新
如果我修改run
直接完成工作,而不是基于QTimer
,线程似乎可以正常工作。即把run
改成:
def run(self):
self.doWork()
return
这并不能解决我的问题,因为我想使用事件队列来运行。因此,我怀疑这是某种信号/插槽问题,其中QTimer
信号与错误的线程相关联。
请注意,在工作完成之前,我不会遇到 exit
或 quit
阻塞。我只是遇到线程根本不起作用。即主窗口被阻止,我什至无法单击按钮以启动退出线程
【问题讨论】:
【参考方案1】:问题在于QThread
方法正在工作。 QThread
的线程亲和性始终是thread that created QThread。因此,信号告诉QThread
的所属线程执行doWork
——在本例中是主线程。所以即使在这个QThread中定义了doWork
,工作还是由主线程完成的。我知道一种心灵扭曲。为了解释,让我从引用文档开始
QThread 对象存在于另一个线程中,即创建它的那个线程。
所以当这个信号/插槽连接建立时
self.timer.timeout.connect(self.doWork)
它,默认是AutoConnection:
(默认)如果信号是从与接收对象不同的线程发出的,则信号将被排队,表现为 Qt::QueuedConnection。否则,槽被直接调用,表现为 Qt::DirectConnection。连接类型在信号发出时确定。
信号的来源是我的QThread,因为QTimer是在run
方法中创建的,但目的地是主线程。它正在主线程的事件队列中排队!解决方案是创建第二个worker QObject,它将具有当前线程的亲和性:
class Worker(QObject):
def __init__(self, parent):
super(Worker, self).__init__(parent=parent)
def doWork(self):
#... do work, post signal to consumer
print "Start work"
for i in range(0,1000):
print "work %i" % i
QThread.msleep(100)
print "Done work"
return
然后运行变成:
def run(self):
""" Setup "pullFiles" to be called once a second"""
print "Running..."
self.worker = Worker(parent=None) #affinity = this thread
self.timer= QTimer() #affinity = this thread
print self.timer.thread()
self.timer.setSingleShot(True)
self.timer.timeout.connect(self.worker.doWork)
self.timer.start(4000)
self.exec_()
print "Exec_ done"
这很有效。信号的来源和目的地都在一个线程中,不会遍历回主线程。瞧!
【讨论】:
弓弦乐器与它有什么关系?来自QThread::exit() 文档:
Tells the thread's event loop to exit with a return code.
您的doWork
是事件循环中的单个事件。事件循环调用了您的事件,因此它等待它完成。 exit
是另一个事件,在事件循环中排队等待doWork
完成。 msleep
有助于您的 GUI 的响应能力(给它时间重新绘制和执行按钮处理程序),但它确实不能让 exit
事件以某种方式潜入。
如果您希望您的doWork
随时可中断,您必须更改您的逻辑。使计时器更频繁地触发,并且只增加一。 exit 然后可以在两者之间随时采取行动。
【讨论】:
是的,我知道这一点,但我希望主线程在等待退出时仍然运行。如果我在出口处挡住我会没事的,但按钮根本无法按下。不过我现在知道答案了,我会发布的。 其实顺便说一句,我创建了这个来测试你的答案 :) 看起来是正确的以上是关于为啥我的 QThread 正在使主线程挨饿?的主要内容,如果未能解决你的问题,请参考以下文章
我可以在属于主线程的 QThread 中使用 waitForReadyRead 吗?