PySide 子线程中的计时器

Posted

技术标签:

【中文标题】PySide 子线程中的计时器【英文标题】:Timer in a Child Thread in PySide 【发布时间】:2012-03-25 17:08:00 【问题描述】:

首先,我对 Python 和 Pyside 非常陌生。为了做一些自我改进,我试图让 QTimer 在我的 PySide 程序的子线程中每秒执行一次(目前我只希望它每秒打印“hi!”到终端不冻结主窗口)。

我尝试将 example I found on the Qt Wiki 从 C++ 转换为 Python/PySide,但由于我并不真正了解 C++,因此我假设我将其转换错误,这就是它无法正常工作的原因。

目前,doWork() 函数似乎只执行一次,然后再也不会执行。我究竟做错了什么?有没有更好的方法在 PySide 中每秒执行一个函数而不冻结主窗口?

这里是代码(我删除了一些主窗口代码以增加清晰度):

from PySide import QtGui
from PySide import QtCore
from client_gui import Ui_MainWindow

statsThread = QtCore.QThread()

class MainWindow(QtGui.QMainWindow, Ui_MainWindow):
    def __init__(self, parent=None):
        super(MainWindow, self).__init__(parent)
        #setup GUI
        self.setupUi(self)
        #start thread to update GUI
        self.statsThread = updateStatsThread()
        self.statsThread.start(QtCore.QThread.TimeCriticalPriority)

class updateGuiWithStats(QtCore.QObject):
    def Worker(self):
        timer = QtCore.QTimer()
        timer.timeout.connect(self.doWork())
        timer.start(1000)

    def doWork(self):
        print "hi!"

class updateStatsThread (QtCore.QThread):
    def run(self):
        updater = updateGuiWithStats()
        updater.Worker()
        self.exec_()

if __name__ == '__main__':
    app = QtGui.QApplication(sys.argv)
    frame = MainWindow()
    frame.show()
    sys.exit(app.exec_())

【问题讨论】:

【参考方案1】:

@Masci 已经指出了您需要对您的timer.timeout.connect 进行修复,但我发现的问题不止于此。

无需创建一个从未使用过的全局 QThread:

statsThread = QtCore.QThread()

您的 QTimer 正在被立即进行垃圾收集,因为它在没有父级的情况下创建,并且您没有将它保存在您的班级中。这就是为什么即使您修复了计时器连接,它仍然无法正常工作...尝试:

class UpdateGuiWithStats(QtCore.QObject):

    def startWorker(self):
        self.timer = QtCore.QTimer()
        self.timer.timeout.connect(self.doWork)
        self.timer.start(1000)

此外,类的首字母使用大写,方法的首字母使用驼峰式。你正在做这两种方式的混合。

基于您提供的链接、您的示例和此处的其他 cmets 的一些注释...如果您的 doWork() 非常轻且不会阻塞您的主事件循环,您可以仅使用 QTimer 作为解决方案有一堆数据处理,睡眠等。如果是这样,那么doWork()将需要移动到QThread,就像你的例子一样。但是在那个时候,使用事件循环和在一个单独的类中调用它自己的工作的 QTimer 是有点不必要的。这一切都可以合并到一个类中,例如:

class UpdateStatsThread(QtCore.QThread):

    def __init__(self, parent=None):
        super(UpdateStatsThread, self).__init__(parent)
        self._running = False

    def run(self):
        self._running = True
        while self._running:
            self.doWork()
            self.msleep(1000)

    def stop(self, wait=False):
        self._running = False
        if wait:
            self.wait()

    def doWork(self):
        print "hi!"

【讨论】:

你是正确的假设我想在doWork() 中做更多的事情,而不仅仅是打电话给print "foo" 或其他东西。考虑到这一点,我实现了您编写的修改后的UpdateStatsThread() 类。但是,当我退出线程时,我收到一条消息,指出线程在运行时被破坏。我认为这是因为我在time.sleep(1) 仍在运行时退出了线程。有没有什么好的方法可以退出话题并避免该消息? 完全是。这绝对是因为 python 在对象真正完成之前就将其破坏了。我更新了我的答案,为 stop() 方法添加了一个可选的等待。我还更新了睡眠以使用 QThread.msleep()【参考方案2】:

updateGuiWithStats 类中,Worker 方法:

timer.timeout.connect(self.doWork())

应该是

timer.timeout.connect(self.doWork)

您正在将超时信号连接到NonedoWork() 方法的返回值),我认为这就是它只执行一次的原因:doWork 在连接期间被调用,不再调用。建立连接时,请记住连接函数名(Python 中的可调用对象)而不是函数调用。

顺便说一句,即使上面解决了你的问题,你也应该避免使用线程,因为 QTimer 已经可以自己做你需要的了。在docs you linked 中,什么时候不应该使用线程? 问题的第一个答案是:计时器。

【讨论】:

另外,使用 QTimer 代替 QThread 的解决方案并不是万能的解决方案。它仅在您的 doWork() 速度快时才有效,这样它就可以被执行而不阻塞主事件循环。如果它是一个繁重的调用,那么每次调用它时它仍然会阻塞你的事件循环。因此,您需要将该功能移动到线程中。 同意,但是对于 OP 的需要,我认为线程是多余的 那是一种毫无根据的假设。你怎么知道他的doWork() 真的在做什么?它显然不是一个简单的打印语句。那只是他的示例代码。 OP 的需求很可能需要一个线程

以上是关于PySide 子线程中的计时器的主要内容,如果未能解决你的问题,请参考以下文章

在子线程中添加定时器并且时间到后退出runloop

在子线程中使用runloop,正确操作NSTimer计时的注意点 三种可选方法

Qt 子线程更新Ui

PySide / PyQtGraph 访问主 Qt 事件线程

java界面子线程界面阻塞了主线程界面怎么解决?

Qt5 自定义线程下使用定时器