为什么我的QThread正在完成工作?

时间:2012-08-15 15:14:51

标签: python multithreading qt pyside

这是我基于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,我希望看到线程开始打印

Starting...
Started...
Start Work
work 0
work 1
...
work 99
Done Work

但我想做的是能够在工作时停止线程。

我希望有所不同
Starting...
Started...
Start Work
work 0
work 1
...
work N
Stopping...
work 99
Done Work
Stopped...

相反,工作线程似乎阻止主线程执行?我必须等待工作完成才能点击startStopButton,给我

Starting...
Started...
Start Work
work 0
work 1
...
work 99
Done Work
Stopping...
Stopped...

doWork运行多久并不重要。我把它提升了10000次循环。 它似乎没有给主线程留出时间,而且小部件没有响应。我做的是阻止真正的线程实际工作吗?

(我使用的是python 2.7和pyside 1.10。)

更新

如果我修改run直接执行工作,而不是基于QTimer线程似乎正常工作。即将run更改为:

def run(self):
    self.doWork()
    return

这不能解决我的问题,因为我想使用事件队列运行。因此,我怀疑这是某种信号/插槽问题,其中QTimer信号与错误的线程相关联。

请注意,在完成工作之前,我不会遇到exitquit个阻止。我只是遇到线程根本无法工作。即主窗口被阻止,我甚至无法点击按钮甚至开始退出线程

2 个答案:

答案 0 :(得分:3)

问题在于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方法中创建的,但目标是主线程。它在主线程的事件队列中排队!解决方案是创建第二个工作者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"

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

答案 1 :(得分:0)

来自QThread::exit()文档:

Tells the thread's event loop to exit with a return code.

您的doWork是事件循环中的单个事件。事件循环调用了您的事件,因此它等待它完成。 exit是另一个事件,在事件循环中排队并等待doWork完成。 msleep有助于GUI的响应性(给它时间来重新绘制和执行按钮处理程序),但它确实不会使exit事件以某种方式潜行。

如果您希望doWork随时可以中断,则必须更改逻辑。使计时器更频繁地点火并且仅增加1。退出然后可以在任何时间进行。