为啥我的 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 信号与错误的线程相关联。

请注意,在工作完成之前,我不会遇到 exitquit 阻塞。我只是遇到线程根本不起作用。即主窗口被阻止,我什至无法单击按钮以启动退出线程

【问题讨论】:

【参考方案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"

这很有效。信号的来源和目的地都在一个线程中,不会遍历回主线程。瞧!

【讨论】:

弓弦乐器与它有什么关系? 【参考方案2】:

来自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 吗?

除了主 ui 线程之外,使用 QThread 设置图形时遇到问题

Qt:将事件发布到 QThread 的正确方法?

从辅助线程调用主线程函数

C++控制台程序怎样使主函数无限循环