背景
在使用pyqt5编写GUI程序时,我们可能有时会需要使用多线程来处理问题,这就可能会涉及到线程之间相互通信的问题。比如接下来要解决的进度条问题。
解决思路
我们可以通过pyqt5的信号与槽机制来完成线程之间的通信。
代码编写
# Copyright (C) 2023 - 2023 wutljs, Inc. All Rights Reserved
# @Time: 2023/12/2 10:12
# @Author: wutljs
# @File: item_1.py
# @IDE: PyCharmimport sys, time
from PyQt5.QtCore import QRect, QThread, pyqtSignal
from PyQt5.QtWidgets import QDialog, QProgressBar, QApplicationclass UiProgressBarDialog:"""主线程类"""def __init__(self, dialog):"""做一些简单的初始化工作"""self.dialog = dialog # 获得空dialogself.dialog.setFixedSize(480, 360) # 设置dialog尺寸self.progressbar = QProgressBar(self.dialog) # 添加进度条self.progressbar.setGeometry(QRect(80, 80, 351, 31)) # 进设置度条尺寸self.progressbar.setValue(0) # 设置进度初始值为0def add_task_thread(self):"""添加子线程,并建立线程通信(重头戏)"""task_thread = TaskThread() # 添加任务子线程(任务线程)task_thread.update_signal.connect(self.update_progressbar) # 连接子线程(任务线程)信号与主线程的槽,搭建线程通信task_thread.start() # 开启子线程(任务线程)self.dialog.exec_() # dialog开始生效(显示)def update_progressbar(self, progressbar_value):"""槽: 用来接收来自子线程(任务线程)发射的信号,并作出响应(更新进度条)"""self.progressbar.setValue(progressbar_value)class TaskThread(QThread):"""子线程类"""update_signal = pyqtSignal(int) # 信号: 通知主线程更新进度条的信号task_total_num = 1000 # 假设任务总量为1000task_finished_num = 0 # 任务完成量def do_task(self):self.task_finished_num += 1 # 更新任务完成量self.update_signal.emit(self.task_finished_num / self.task_total_num * 100) # 子线程(任务线程)发射信号,通知主线程更新进度条的值为此信号所携带的值.def run(self):# 模拟完成任务for i in range(self.task_total_num):self.do_task() # 模拟做任务time.sleep(0.001) # 防止进度条更新过快不便于观察if __name__ == '__main__':app = QApplication(sys.argv)ui = UiProgressBarDialog(QDialog())ui.add_task_thread()
运行结果

结论
当我们使用pyqt5进行可视化编程时,或许我们总能碰到主线程卡死的情况。除了代码编写的有问题,有一种可能就是有一些非常耗时、占用资源的代码在运行时阻塞着主线程,故此时需要使用多线程的方法来解决问题。而在代码中,我们看到使用信号与槽这一机制可以很好地完成线程之间的通信问题。
展望
显然,这个程序有着很强的可扩展性:
- 将TaskThread的run方法抽离出来,把这个进度条代码变成装饰器,可以很方便的看到被修饰代码运行进度。
- 这段代码可以与异步协程结合。使用协程解决的问题一般都是任务量比较大的问题,正好可以用进度条来记录当前任务进度。不过如果真有此打算的话,代码还需要补充一些必要的东西,碰巧笔者有一些经验,欢迎探讨。
- 将这个进度条的UI使用html、js或者css润色加工一下,放在项目中感觉也还可以。
除此以外,信号与槽机制的应用显然不止这些,感兴趣的朋友可以自行探索