wutljs 发表于 2023-12-2 17:05

讨论GUI库pyqt5的中线程通信问题

本帖最后由 wutljs 于 2023-12-2 19:44 编辑

# 讨论pyqt5的中线程通信

## 背景

在使用pyqt5编写GUI程序时,我们可能有时会需要使用多线程来处理问题,这就可能会涉及到线程之间相互通信的问题。比如接下来要解决的进度条问题。

## 解决思路

我们可以通过pyqt5的**信号与槽机制**来完成线程之间的通信。

## 代码编写

```python
# Copyright (C) 2023 - 2023 wutljs, Inc. All Rights Reserved
# @Time: 2023/12/2 10:12
# @Author: wutljs
# @File: item_1.py
# @IDE: PyCharm

import sys, time
from PyQt5.QtCore import QRect, QThread, pyqtSignal
from PyQt5.QtWidgets import QDialog, QProgressBar, QApplication


class UiProgressBarDialog:
    """主线程类"""

    def __init__(self, dialog):
      """做一些简单的初始化工作"""

      self.dialog = dialog# 获得空dialog
      self.dialog.setFixedSize(480, 360)# 设置dialog尺寸
      self.progressbar = QProgressBar(self.dialog)# 添加进度条
      self.progressbar.setGeometry(QRect(80, 80, 351, 31))# 进设置度条尺寸
      self.progressbar.setValue(0)# 设置进度初始值为0

    def 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# 假设任务总量为1000
    task_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润色加工一下,放在项目中感觉也还可以。

除此以外,**信号与槽**机制的应用显然不止这些,感兴趣的朋友可以自行探索~

## 参考文章

(https://blog.csdn.net/huayunhualuo/article/details/102718509)

(https://www.cnblogs.com/linyfeng/p/12239856.html)

wutljs 发表于 2023-12-4 11:54

lookfeiji 发表于 2023-12-4 10:57
不对,不是线程问题,是多线程的机制与通信的问题,tkinter没提供多线程的方法,python的多线程都被说是 ...

确实,python因为GIL缘故不像java那样可以搞多线程(Hadoop等框架都是java写的)。
pyqt5可以解决您的问题,查相关文档即可~

aqin5014 发表于 2023-12-3 10:34

最近刚好在学习这个,支持你一波{:1_918:}

Cacarot 发表于 2023-12-3 17:54

一个时间倒数子线程CPU占用过高,看看有没有帮助

wutljs 发表于 2023-12-3 18:12

aqin5014 发表于 2023-12-3 10:34
最近刚好在学习这个,支持你一波

谢谢啦,祝你顺利!

wutljs 发表于 2023-12-3 20:28

本帖最后由 wutljs 于 2023-12-3 20:30 编辑

Cacarot 发表于 2023-12-3 17:54
一个时间倒数子线程CPU占用过高,看看有没有帮助
嗯~这篇文章的逻辑是将耗时任务放在子线程,防止主线程卡死。线程间交互的方式为信号与槽机制~
如果该耗时任务真的很棘手,建议使用节点分布式等手段完成。

lookfeiji 发表于 2023-12-4 08:36

收下代码先,我在tkinter上也碰到了这个问题,但是遗憾的是我没找到解决办法,最后放弃用进度条了

wutljs 发表于 2023-12-4 09:14

lookfeiji 发表于 2023-12-4 08:36
收下代码先,我在tkinter上也碰到了这个问题,但是遗憾的是我没找到解决办法,最后放弃用进度条了

嗯,之前我尝试使用threading开线程作为子线程,但依然使主线程处于假死的状态。后来使用pyqt5中特有的线程通信机制才解决了问题。tkinter没怎么接触过,不过您可以试试tkinter提供的多线程方法?

lookfeiji 发表于 2023-12-4 10:20

wutljs 发表于 2023-12-4 09:14
嗯,之前我尝试使用threading开线程作为子线程,但依然使主线程处于假死的状态。后来使用pyqt5中特有的线 ...

谢谢,你这给了我一个很好的提示,我问gpt他说tkinter没有多线程方法,但是我在百度居然搜到了,研究研究去

lookfeiji 发表于 2023-12-4 10:57

wutljs 发表于 2023-12-4 09:14
嗯,之前我尝试使用threading开线程作为子线程,但依然使主线程处于假死的状态。后来使用pyqt5中特有的线 ...

不对,不是线程问题,是多线程的机制与通信的问题,tkinter没提供多线程的方法,python的多线程都被说是假的,主要没法实现线程与线程之间的通信,还有就是基本的线程关闭,以及线程与主线程之间的调度都有点难搞
页: [1]
查看完整版本: 讨论GUI库pyqt5的中线程通信问题