【问题标题】:Timer in a Child Thread in PySidePySide 子线程中的计时器
【发布时间】: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_())

【问题讨论】:

    标签: python qt pyside


    【解决方案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 的需求很可能需要一个线程
    猜你喜欢
    • 2013-08-22
    • 2016-07-27
    • 2018-04-30
    • 1970-01-01
    • 1970-01-01
    • 2011-02-18
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多