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)
您正在将超时信号连接到None
(doWork()
方法的返回值),我认为这就是它只执行一次的原因:doWork
在连接期间被调用,不再调用。建立连接时,请记住连接函数名(Python 中的可调用对象)而不是函数调用。
顺便说一句,即使上面解决了你的问题,你也应该避免使用线程,因为 QTimer 已经可以自己做你需要的了。在docs you linked 中,什么时候不应该使用线程? 问题的第一个答案是:计时器。
【讨论】:
另外,使用 QTimer 代替 QThread 的解决方案并不是万能的解决方案。它仅在您的doWork()
速度快时才有效,这样它就可以被执行而不阻塞主事件循环。如果它是一个繁重的调用,那么每次调用它时它仍然会阻塞你的事件循环。因此,您需要将该功能移动到线程中。
同意,但是对于 OP 的需要,我认为线程是多余的
那是一种毫无根据的假设。你怎么知道他的doWork()
真的在做什么?它显然不是一个简单的打印语句。那只是他的示例代码。 OP 的需求很可能需要一个线程以上是关于PySide 子线程中的计时器的主要内容,如果未能解决你的问题,请参考以下文章
在子线程中使用runloop,正确操作NSTimer计时的注意点 三种可选方法