PySide中子线程中的定时器

3
首先,我非常新于Python和Pyside。为了进行一些自我提高,我试图在我的PySide程序的子线程中使QTimer每秒执行一次(目前我只想让它每秒打印“hi!”到终端而不冻结主窗口)。
我尝试将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_())
2个回答

4

@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)

此外,类的第一个字母应该使用大写字母,方法应该使用驼峰命名法。你目前混合了两种方式。
根据你提供的链接、示例和其他评论,有几点需要注意。如果你的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) 仍在运行时就退出了线程。有什么好的方法可以退出线程并避免出现该消息吗? - James Mackenzie
完全正确。这肯定是因为Python在对象真正完成之前就销毁了它。我更新了我的答案,添加了一个可选的等待stop()方法。我还更新了sleep以使用QThread.msleep()。 - jdi

1
updateGuiWithStats 类中的 Worker 方法:
timer.timeout.connect(self.doWork())

应该是

timer.timeout.connect(self.doWork)

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

顺便说一下,即使上述解决了您的问题,您也应该避免使用线程,因为QTimer已经可以自己完成所需的操作。在您提供的文档中,对于“何时不应使用线程?”问题的第一个答案是:定时器。


此外,使用 QTimer 而不是 QThread 的解决方案并非万能之策。它只适用于 doWork() 很快就能执行而不会阻塞主事件循环的情况。如果这是一个繁重的调用,那么每次调用它仍然会阻塞您的事件循环。因此,您需要将该功能移入线程中。 - jdi
同意,但考虑到楼主的需求,我认为使用线程是过度设计。 - Masci
这有点毫无根据的假设。你怎么知道他的 doWork() 真正做了什么?显然它不是一个简单的打印语句。那只是他的示例代码。OP 的需求很可能需要一个线程。 - jdi

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接