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

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 信号会携带一个字符串参数。

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

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

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

完整代码

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_())

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

欢迎学习交流

  • 16
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
PyQt中,如果在主界面线程中执行耗时操作,会导致界面卡死的问题。这是因为PyQt应用程序是基于事件驱动的,主线程负责处理GUI事件。当有耗时的操作任务时,GUI事件会被阻塞,导致应用程序处于假死状态,无法与应用程序进行交互。为了解决这个问题,可以采用多线程的方式。一种方式是继承自QThread类,将耗时的操作放在子线程中进行处理,然后通过signal-slot机制将子线程的数据反馈到主界面线程中。需要注意的是,在子线程中不能操作界面,只能处理数据。这样可以将UI的操作与耗时数据的处理进行分开处理,避免了界面卡死的问题。另一种方式是使用RunThread类继承自QObject,而非继承自QThread。这种方式将数据的处理与线程的创建与启动分开进行处理,适用于某些场景下比较方便。虽然这种方式比较复杂,但在特定情况下可以更灵活地使用多线程。\[1\]\[2\]\[3\] #### 引用[.reference_title] - *1* *2* [PyQt - 使用多线程避免界面卡顿](https://blog.csdn.net/bailang_zhizun/article/details/109240670)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^insert_down1,239^v3^insert_chatgpt"}} ] [.reference_item] - *3* [解决PySide6/PyQT的界面卡死问题(PySide6/PyQT多线程](https://blog.csdn.net/weixin_45081575/article/details/130210522)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^insert_down1,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Wu.Nim

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值