Qthread 上的 QTimer
QTimer on a Qthread
我有一个 GUI 我需要使用 Qtimer 不断更新,为此我使用了一个 worker Qthread,这是我的代码 :
from PyQt5.QtWidgets import QApplication, QPushButton, QWidget
from PyQt5.QtCore import QThread, QTimer
import sys
import threading
class WorkerThread(QThread):
def run(self):
print("thread started from :" + str(threading.get_ident()))
timer = QTimer(self)
timer.timeout.connect(self.work)
timer.start(5000)
self.exec_()
def work(self):
print("working from :" + str(threading.get_ident()))
QThread.sleep(5)
class MyGui(QWidget):
worker = WorkerThread()
def __init__(self):
super().__init__()
self.initUi()
print("Starting worker from :" + str(threading.get_ident()))
self.worker.start()
def initUi(self):
self.setGeometry(500, 500, 300, 300)
self.pb = QPushButton("Button", self)
self.pb.move(50, 50)
self.show()
app = QApplication(sys.argv)
gui = MyGui()
app.exec_()
输出是:
Starting worker from :824
thread started from :5916
working from :824
working from :824
计时器正在主线程上工作冻结我的 Gui,我该如何解决?
对不起,我误解了问题。另一个问题的答案可能对您有所帮助。主要信息是您应该使用 Qt 中的主事件循环来不冻结 GUI 而不是在 __init__
上执行线程:
您可以通过将 Qt 插槽与装饰器一起使用来做到这一点 @pyqtSlot()
。
------------旧(错误)答案--------
QTimer
已经可以在单独的线程上工作,所以我认为您无需自己编写该部分就可以做到。例如,您可以执行您已经在函数中执行的操作:
def update_gui(self):
# edit: here is where you can add your gui update code:
self.setGeometry(500, 500, 300, 300)
self.pb = QPushButton("Button", self)
self.pb.move(50, 50)
self.show()
# /edit (this is obviously only the setup code, but you get the idea)
self.update_timer = QTimer()
self.update_timer.setInterval(int(5000))
self.update_timer.timeout.connect(self.update_gui)
self.update_timer.start()
并在__init__
中调用它。这就是我如何实现一些在几秒钟后自行清除的文本框。
试一试:
import sys
import threading
from PyQt5.QtGui import *
from PyQt5.QtCore import *
from PyQt5.QtWidgets import *
class WorkerThread(QThread):
workSignal = pyqtSignal(str)
def run(self):
print("thread started from :" + str(threading.get_ident()))
textLabel = "thread started from :" + str(threading.get_ident())
self.workSignal.emit(textLabel)
self.work()
def work(self):
print("working from :" + str(threading.get_ident()))
textLabel = "working from :" + str(threading.get_ident())
self.workSignal.emit(textLabel)
class MyGui(QWidget):
worker = WorkerThread()
def __init__(self):
super().__init__()
self.initUi()
print("Starting worker from :" + str(threading.get_ident()))
self.lbl.setText("Starting worker from :" + str(threading.get_ident()))
self.worker.workSignal.connect(self.showLabel)
def initUi(self):
self.setGeometry(700, 350, 300, 150)
self.lcdTime = QLCDNumber(self)
self.lcdTime.setSegmentStyle(QLCDNumber.Filled) # Outline Filled Flat
self.lcdTime.setDigitCount(8)
self.timer = QTimer(self)
self.lbl = QLabel(self)
self.pb = QPushButton("Button Close", self, clicked=self.close)
vbox = QVBoxLayout()
vbox.addWidget(self.lcdTime)
vbox.addWidget(self.lbl)
vbox.addWidget(self.pb)
self.setLayout(vbox)
self.timer.timeout.connect(self.showTime)
self.timer.start(1000)
self.numSec = 0
self.show()
def showTime(self):
time = QTime.currentTime()
text = time.toString("hh:mm:ss")
if ((time.second() % 2) == 0):
text = text[0:2] + ' ' + text[3:5] + ' ' + text[6:]
self.lcdTime.display(text)
self.numSec += 1
if self.numSec == 5:
self.worker.start()
self.numSec = 0
def showLabel(self, textLabel):
self.lbl.setText(textLabel)
app = QApplication(sys.argv)
gui = MyGui()
app.exec_()
回答:在你的情况下我看不出需要使用QThread
。
TL;博士;
什么时候需要在 GUI 上下文中使用另一个线程?
当某些任务可以阻塞称为GUI线程的主线程时,应该只使用一个线程,并且阻塞是由于任务耗时导致GUI事件循环无法正常工作而引起的。所有现代 GUI 都在一个事件循环中执行,它允许您从 OS 接收通知,如键盘、鼠标等,还允许您根据用户修改 GUI 的状态。
在你的情况下,我没有看到任何繁重的任务,所以我没有看到需要 QThread,我真的不知道你想要定期 运行 的任务是什么。
假设你有一个很耗时的任务,比如30秒,你必须每半小时做一次,那么线程是必须的。在你的情况下,你想为它使用 QTimer。
我们分两部分来看,QTimer
是继承自QObject
的class,而QObject
与parent属于同一个,如果它没有父线程,它属于创建它的线程。另一方面,很多时候人们认为 QThread
是 Qt 的 线程,但事实并非如此,QThread
是 class允许处理本机线程的生命周期,docs 中明确说明了这一点:QThread class 提供了一种独立于平台的方式来 管理线程.
了解了以上内容,我们来分析一下你的代码:
timer = QTimer(self)
在上面的代码中,self是QTimer的父线程,self是QThread
,所以QTimer
属于QThread
的父线程或者QThread创建的地方,不是 QThread
处理的线程。
然后让我们看看创建QThread
的代码:
worker = WorkerThread()
我们看到QThread
没有父线程,那么QThread
属于创建它的线程,也就是QThread
属于主线程,因此它的QTimer
child也属于主线程。另请注意,QThread
处理的新线程只有run()
方法的范围,如果该方法在其他地方则属于创建QThread
的字段,以上我们看到代码的输出是正确的,主线程上的 QThread.sleep(5)
运行s 导致事件循环崩溃和 GUI 冻结。
所以解决方法是去掉QTimer
的父线程,使其所属的线程是run()
方法中的线程,将工作函数移到同一个方法中。另一方面,不必要地创建静态属性是一种不好的做法,考虑到上述结果代码如下:
import sys
import threading
from PyQt5.QtCore import QThread, QTimer
from PyQt5.QtWidgets import QApplication, QPushButton, QWidget
class WorkerThread(QThread):
def run(self):
def work():
print("working from :" + str(threading.get_ident()))
QThread.sleep(5)
print("thread started from :" + str(threading.get_ident()))
timer = QTimer()
timer.timeout.connect(work)
timer.start(10000)
self.exec_()
class MyGui(QWidget):
def __init__(self):
super().__init__()
self.initUi()
self.worker = WorkerThread(self)
print("Starting worker from :" + str(threading.get_ident()))
self.worker.start()
def initUi(self):
self.setGeometry(500, 500, 300, 300)
self.pb = QPushButton("Button", self)
self.pb.move(50, 50)
if __name__ == '__main__':
app = QApplication(sys.argv)
gui = MyGui()
gui.show()
sys.exit(app.exec_())
输出:
Starting worker from :140068367037952
thread started from :140067808999168
working from :140067808999168
working from :140067808999168
观察:
已经模拟的繁重任务是5秒,那个任务必须每10秒执行一次。如果您的任务花费的时间比您应该创建其他线程的时间长。
如果你的任务是执行一个没有显示时间那么繁重的周期性任务,那么不要使用新线程,因为你正在为一个简单的任务增加复杂性,此外这可能会导致调试和测试阶段更复杂。
我有一个 GUI 我需要使用 Qtimer 不断更新,为此我使用了一个 worker Qthread,这是我的代码 :
from PyQt5.QtWidgets import QApplication, QPushButton, QWidget
from PyQt5.QtCore import QThread, QTimer
import sys
import threading
class WorkerThread(QThread):
def run(self):
print("thread started from :" + str(threading.get_ident()))
timer = QTimer(self)
timer.timeout.connect(self.work)
timer.start(5000)
self.exec_()
def work(self):
print("working from :" + str(threading.get_ident()))
QThread.sleep(5)
class MyGui(QWidget):
worker = WorkerThread()
def __init__(self):
super().__init__()
self.initUi()
print("Starting worker from :" + str(threading.get_ident()))
self.worker.start()
def initUi(self):
self.setGeometry(500, 500, 300, 300)
self.pb = QPushButton("Button", self)
self.pb.move(50, 50)
self.show()
app = QApplication(sys.argv)
gui = MyGui()
app.exec_()
输出是:
Starting worker from :824
thread started from :5916
working from :824
working from :824
计时器正在主线程上工作冻结我的 Gui,我该如何解决?
对不起,我误解了问题。另一个问题的答案可能对您有所帮助。主要信息是您应该使用 Qt 中的主事件循环来不冻结 GUI 而不是在 __init__
上执行线程:
您可以通过将 Qt 插槽与装饰器一起使用来做到这一点 @pyqtSlot()
。
------------旧(错误)答案--------
QTimer
已经可以在单独的线程上工作,所以我认为您无需自己编写该部分就可以做到。例如,您可以执行您已经在函数中执行的操作:
def update_gui(self):
# edit: here is where you can add your gui update code:
self.setGeometry(500, 500, 300, 300)
self.pb = QPushButton("Button", self)
self.pb.move(50, 50)
self.show()
# /edit (this is obviously only the setup code, but you get the idea)
self.update_timer = QTimer()
self.update_timer.setInterval(int(5000))
self.update_timer.timeout.connect(self.update_gui)
self.update_timer.start()
并在__init__
中调用它。这就是我如何实现一些在几秒钟后自行清除的文本框。
试一试:
import sys
import threading
from PyQt5.QtGui import *
from PyQt5.QtCore import *
from PyQt5.QtWidgets import *
class WorkerThread(QThread):
workSignal = pyqtSignal(str)
def run(self):
print("thread started from :" + str(threading.get_ident()))
textLabel = "thread started from :" + str(threading.get_ident())
self.workSignal.emit(textLabel)
self.work()
def work(self):
print("working from :" + str(threading.get_ident()))
textLabel = "working from :" + str(threading.get_ident())
self.workSignal.emit(textLabel)
class MyGui(QWidget):
worker = WorkerThread()
def __init__(self):
super().__init__()
self.initUi()
print("Starting worker from :" + str(threading.get_ident()))
self.lbl.setText("Starting worker from :" + str(threading.get_ident()))
self.worker.workSignal.connect(self.showLabel)
def initUi(self):
self.setGeometry(700, 350, 300, 150)
self.lcdTime = QLCDNumber(self)
self.lcdTime.setSegmentStyle(QLCDNumber.Filled) # Outline Filled Flat
self.lcdTime.setDigitCount(8)
self.timer = QTimer(self)
self.lbl = QLabel(self)
self.pb = QPushButton("Button Close", self, clicked=self.close)
vbox = QVBoxLayout()
vbox.addWidget(self.lcdTime)
vbox.addWidget(self.lbl)
vbox.addWidget(self.pb)
self.setLayout(vbox)
self.timer.timeout.connect(self.showTime)
self.timer.start(1000)
self.numSec = 0
self.show()
def showTime(self):
time = QTime.currentTime()
text = time.toString("hh:mm:ss")
if ((time.second() % 2) == 0):
text = text[0:2] + ' ' + text[3:5] + ' ' + text[6:]
self.lcdTime.display(text)
self.numSec += 1
if self.numSec == 5:
self.worker.start()
self.numSec = 0
def showLabel(self, textLabel):
self.lbl.setText(textLabel)
app = QApplication(sys.argv)
gui = MyGui()
app.exec_()
回答:在你的情况下我看不出需要使用QThread
。
TL;博士;
什么时候需要在 GUI 上下文中使用另一个线程?
当某些任务可以阻塞称为GUI线程的主线程时,应该只使用一个线程,并且阻塞是由于任务耗时导致GUI事件循环无法正常工作而引起的。所有现代 GUI 都在一个事件循环中执行,它允许您从 OS 接收通知,如键盘、鼠标等,还允许您根据用户修改 GUI 的状态。
在你的情况下,我没有看到任何繁重的任务,所以我没有看到需要 QThread,我真的不知道你想要定期 运行 的任务是什么。
假设你有一个很耗时的任务,比如30秒,你必须每半小时做一次,那么线程是必须的。在你的情况下,你想为它使用 QTimer。
我们分两部分来看,QTimer
是继承自QObject
的class,而QObject
与parent属于同一个,如果它没有父线程,它属于创建它的线程。另一方面,很多时候人们认为 QThread
是 Qt 的 线程,但事实并非如此,QThread
是 class允许处理本机线程的生命周期,docs 中明确说明了这一点:QThread class 提供了一种独立于平台的方式来 管理线程.
了解了以上内容,我们来分析一下你的代码:
timer = QTimer(self)
在上面的代码中,self是QTimer的父线程,self是QThread
,所以QTimer
属于QThread
的父线程或者QThread创建的地方,不是 QThread
处理的线程。
然后让我们看看创建QThread
的代码:
worker = WorkerThread()
我们看到QThread
没有父线程,那么QThread
属于创建它的线程,也就是QThread
属于主线程,因此它的QTimer
child也属于主线程。另请注意,QThread
处理的新线程只有run()
方法的范围,如果该方法在其他地方则属于创建QThread
的字段,以上我们看到代码的输出是正确的,主线程上的 QThread.sleep(5)
运行s 导致事件循环崩溃和 GUI 冻结。
所以解决方法是去掉QTimer
的父线程,使其所属的线程是run()
方法中的线程,将工作函数移到同一个方法中。另一方面,不必要地创建静态属性是一种不好的做法,考虑到上述结果代码如下:
import sys
import threading
from PyQt5.QtCore import QThread, QTimer
from PyQt5.QtWidgets import QApplication, QPushButton, QWidget
class WorkerThread(QThread):
def run(self):
def work():
print("working from :" + str(threading.get_ident()))
QThread.sleep(5)
print("thread started from :" + str(threading.get_ident()))
timer = QTimer()
timer.timeout.connect(work)
timer.start(10000)
self.exec_()
class MyGui(QWidget):
def __init__(self):
super().__init__()
self.initUi()
self.worker = WorkerThread(self)
print("Starting worker from :" + str(threading.get_ident()))
self.worker.start()
def initUi(self):
self.setGeometry(500, 500, 300, 300)
self.pb = QPushButton("Button", self)
self.pb.move(50, 50)
if __name__ == '__main__':
app = QApplication(sys.argv)
gui = MyGui()
gui.show()
sys.exit(app.exec_())
输出:
Starting worker from :140068367037952
thread started from :140067808999168
working from :140067808999168
working from :140067808999168
观察:
已经模拟的繁重任务是5秒,那个任务必须每10秒执行一次。如果您的任务花费的时间比您应该创建其他线程的时间长。
如果你的任务是执行一个没有显示时间那么繁重的周期性任务,那么不要使用新线程,因为你正在为一个简单的任务增加复杂性,此外这可能会导致调试和测试阶段更复杂。