Python 了解多线程QThread-解决UI界面卡死情况

时间:2025-05-10 07:12:28

Python 了解多线程QThread-解决UI界面卡住情况

项目介绍

在 GUI 程序中,如果想要直接在 UI 线程中进行这些操作会导致整个界面卡住,出现无响应的状态。为了避免这种情况,可以将这些耗时任务放在另一个线程中执行。在 PyQt 中,可以使用 QThread 来实现这一点。

编译环境

Python版本:Python3.8及以上
PyQt版本: PyQt5

问题分析

通常我们会对UI界面进行更新,比如QTextEdit会不断更新实时日志,如下

	def buttonClick(self):
		self.confusionLogEdit("点击按钮追加信息")
		
    def updateConfusionLogEdit(self, msg):
        self.confusionLogEdit.append(msg)

但是我们运行程序后发现,界面并没有刷新,直到程序结束后,所有日志才会同时显示。显然,这不是我们想要的效果。那么,我们一起来研究下如何解决这个问题吧,涉及UI界面卡主问题都可以尝试使用这种方法来处理。

基本用法

在PyQt5中,QThread 类提供了一种方便的方式来实现多线程。多线程允许我们在应用程序中执行一些耗时的任务,而不会阻塞主线程,从而保持用户界面的响应性。

创建子类: 通常,我们会创建一个继承自QThread的子类,该子类将包含我们希望在独立线程中执行的任务。这个子类可以包含信号,以便在线程中定期发送消息或结果给主线程。

from PyQt5.QtCore import QThread, pyqtSignal
 
class RunThread(QThread):
    updateSignal = pyqtSignal(str)
 
    def run(self):
        # 在这里执行耗时任务
        self.updateSignal.emit("任务完成")
  1. 重写 run 方法:在QThread的子类中,我们需要重写run方法。run方法包含了在线程中实际执行的任务。在这个例子中,我们发射了一个信号来通知任务完成。
  2. 使用信号和槽进行通信: 由于线程不能直接访问GUI元素,我们需要使用信号和槽机制来进行线程间通信。在上面的例子中,updateSignal信号用于在工作线程中发射消息,而在主线程中我们可以连接这个信号到一个槽函数,以处理这个消息。

那么我们如何使用这个子类呢?

	def __init__(self):
        super().__init__()
        self.work = RunThread()
	# 按钮点击
    def buttonClick(self):
    	# 绑定信号槽
        self.work.updateSignal.connect(self.updateConfusionLogEdit)
        # 启动子线程
        self.work.start()
    # 编辑框追加信息
    def updateConfusionLogEdit(self, msg):
        self.myLabel.append(msg)

启动和管理线程: 一旦创建了QThread的子类实例,我们可以通过调用start方法来启动线程。通常,我们会在主线程中创建线程实例,并在需要时启动它们。线程的生命周期应该由我们负责管理,包括在适当的时机停止线程。

1.在 PyQt 程序中,主线程就是所说的 UI 线程,UI 线程会处理所有控件的事务。因此,如果有耗时的工作需要执行,通常不会将其放在 UI 线程中,因为这样做会阻止其他控件的更新,导致界面卡顿或程序无响应。
2.如果有耗时操作,在 RunThread 中新增个信号updateSignal。 updateSignal 信号在执行过程中发送。当需要自定义信号时,使用 pyqtSignal 来定义要发送到目标函数的函数原型,例如在下面的示例中,updateSignal = pyqtSignal(str) 表示 updateSignal 信号会携带一个字符串参数。

整个程序的工作流程是:当按下按钮后,会启动另一个线程,这个线程每一秒更新一次秒数到标签上。通过 (str)发送 updateSignal 信号,并传递当前的秒数作为参数。当到达第 5 秒时,线程结束。

注意事项:绑定信号槽时(),不要在点击事件处添加,不然会出现耗时操作重复执行的情况。

⭐️如果对你有用的话,希望可以点点赞,感谢了⭐️

完整代码

import sys
from PyQt5.QtWidgets import QApplication, QWidget, QVBoxLayout, QPushButton, QTextEdit
from PyQt5.QtCore import QThread, pyqtSignal


class RunThread(QThread):
    updateSignal = pyqtSignal(str)

    def run(self):
        for i in range(5):
            time.sleep(1)
            self.updateSignalStr("测试UI更新信息: " + str(i))

    def updateSignalStr(self, str):
        self.updateSignal.emit(str)

class MainClass(QWidget):
    def __init__(self):
        super().__init__()
        self.work = RunThread()
        self.myLabel = None
        self.myBtn = None
        self.initUI()

    def initUI(self):
        self.setWindowTitle('my window')
        self.setGeometry(50, 50, 300, 350)
        layout = QVBoxLayout()
        self.setLayout(layout)
        self.myLabel = QTextEdit('press button to start thread', self)
        layout.addWidget(self.myLabel)
        self.myBtn = QPushButton('start', self)
        self.myBtn.clicked.connect(self.buttonClick)
        layout.addWidget(self.myBtn)
		# 绑定信号槽 保证线程只绑定一次
        self.work.updateSignal.connect(self.updateConfusionLogEdit)

	# 按钮点击
    def buttonClick(self):
    	
        # 启动子线程
        self.work.start()

	# 编辑框追加信息
    def updateConfusionLogEdit(self, msg):
        self.myLabel.append(msg)


if __name__ == '__main__':
    app = QApplication(sys.argv)
    w = MainClass()
    w.show()
    sys.exit(app.exec_())

⭐️如果对你有用的话,希望可以点点赞,感谢了⭐️

欢迎学习交流