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属于主线程,因此它的QTimerchild也属于主线程。另请注意,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秒执行一次。如果您的任务花费的时间比您应该创建其他线程的时间长。

  • 如果你的任务是执行一个没有显示时间那么繁重的周期性任务,那么不要使用新线程,因为你正在为一个简单的任务增加复杂性,此外这可能会导致调试和测试阶段更复杂。