Qthread 上的 QTimer

inf*_*try 3 python pyqt qthread qtimer pyqt5

我有一个 GUI,我需要使用 Qtimer 不断更新,因为我使用工作 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_()
Run Code Online (Sandbox Code Playgroud)

输出是:

Starting worker from :824
thread started from :5916
working from :824
working from :824
Run Code Online (Sandbox Code Playgroud)

计时器正在主线程上工作,冻结了我的 Gui,我该如何解决这个问题?

eyl*_*esc 6

回答:在你的情况下,我认为没有必要使用QThread.

TL; 博士;

什么时候需要在 GUI 上下文中使用另一个线程?

当某些任务可以阻塞称为 GUI 线程的主线程时,只应使用一个线程,并且由于任务耗时而导致阻塞,从而阻止 GUI 事件循环正常执行其工作。所有现代 GUI 都在事件循环中执行,该事件循环允许您从操作系统(如键盘、鼠标等)接收通知,还允许您根据用户修改 GUI 的状态。

在你的情况下,我没有看到任何繁重的任务,所以我没有看到 QThread 的需要,我真的不知道你想要定期运行的任务是什么。

假设你有一个消耗大量时间的任务,比如说30秒,你必须每半小时做一次,那么线程是必要的。在你的情况下,你想使用 QTimer 。

让我们分部分来看,QTimer是一个继承自 a 的类QObject,并且 a 与QObject父级属于同一类,如果它没有父级,则属于创建它的线程。另一方面,很多时候人们认为 aQThreadQt 的线程,但事实并非如此,QThread它是一个允许处理本机线程生命周期的类,并且在文档中明确说明 QThread类提供一种独立于平台的线程管理方式

知道了上面的内容,我们来分析一下你的代码:

timer = QTimer(self)
Run Code Online (Sandbox Code Playgroud)

在上面的代码中,self是QTimer的父级,self是QTimer的父级QThread,所以QTimer属于QThread的父级QThread或创建QThread的线程,而不是QThread处理的线程。

然后让我们看看创建的代码QThread

worker = WorkerThread()
Run Code Online (Sandbox Code Playgroud)

正如我们所看到的QThread,没有父线程,则QThread属于创建它的线程,即QThread属于主线程,因此它的QTimer子线程也属于主线程。另请注意,处理的新线程QThread仅具有该方法的范围run(),如果该方法在其他地方,则属于创建的字段QThread,通过以上所有内容我们可以看到代码的输出是正确的,并且QThread.sleep(5)在 main 上运行导致事件循环崩溃和 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_())
Run Code Online (Sandbox Code Playgroud)

输出:

Starting worker from :140068367037952
thread started from :140067808999168
working from :140067808999168
working from :140067808999168
Run Code Online (Sandbox Code Playgroud)

观察结果:

  • 已模拟的繁重任务为 5 秒,该任务必须每 10 秒执行一次。如果您的任务花费的时间超过了您应该创建其他线程的时间。

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