PyQt之科学使用线程处理耗时任务以及线程通信方法 您所在的位置:网站首页 pyqt5进程暂停 PyQt之科学使用线程处理耗时任务以及线程通信方法

PyQt之科学使用线程处理耗时任务以及线程通信方法

2023-08-27 10:12| 来源: 网络整理| 查看: 265

目录

前言

PyQt线程科学用法

非科学用法样例

科学用法

线程类

线程通信

线程类在主界面实例化与使用

开启线程

补充(信号的方式实现线程双向通信):

线程类

线程实例化与开启线程挂在后台

发送信号,线程处理数据

结语

参考文章

 

前言

 本文主要讲解PyQt使用多线程模块QThread解决PyQt界面程序执行耗时操作时,程序卡顿出现的无响应以及界面输出无法实时显示的问题。有时候,我们写的程序界面出现未响应,是因为把需要长时间运行的代码放在了主线程,阻塞了事件循环。

QtCore.QThread是一个管理线程的类,当我们使用其构造函数的时候,便新建了一个线程。这里要强调,QThread是一个线程管理器,不要把业务逻辑放在这个类里面,Qt的作者已经多次批评继承QThread类来实现业务逻辑的做法。然而我在网上看了很多资料或者博客,很大一部分都是继承QThread实现实现业务逻辑...当然也有很多科学使用的教程,不过相对来说较少。我今天就把我的科学使用PyQt多线程,心里路程分享出来,供大家参考。

PyQt线程科学用法 非科学用法样例

上面介绍了非科学用法,是直接继承QThread在里面写业务。那么网上很多教程也就是这样的....

class Thread(QThread): def __init__(self): super(Thread,self).__init__() def run(self): #业务逻辑代码 #创建一个新的线程 thread = Thread() thread.start() 科学用法

Qt的作者已经多次批评继承QThread类来实现业务逻辑的做法,那么怎么使用才是科学使用PyQt多线程呢?答案如下:

自己定义一个线程类,这个类要继承QObject,在里面实现线程的相关业务逻辑,同时在主界面里实例化这个线程类,然后用moveToThread方法移动到QThread管理。

# fixme PyQt线程科学用法 self.thread = QThread() #实例外线程对象 workThread是自己写的线程类,后面会贴出来 self.work_thread = workThread() # 把实例化的线程用moveToThread移到QThread管理 self.work_thread.moveToThread(self.thread) 线程类

首先写好自己的线程类,实现业务功能 ,因为我这里是由于图像处理耗时,直接把原来的代码贴过来,稍加修改的。要注意的是线程之间的通信,以及是否会有资源竞争等情况。 QtCore.Signal和QtCore.pyqtSignal是一样的,我这里这样写是开源的labelme是这样的,我就采用原样写法。都是信号定义的方式。

class workThread(QObject): #图像处理完成信号 to_show_img_signal = QtCore.Signal(QtGui.QImage) def __init__(self): super(workThread, self).__init__() def work(self): global imageData global mask_list """ 省略图像 处理相关代码 """ #处理完成发送信号 self.to_show_img_signal.emit(qimage) 线程通信

线程通信的方式:全局变量,消息传递(PyQt信号槽机制)

我这里采用的是全局变量,在Mianwindow类和workThread类之外定义了两个全局变量,在两个类使用到改变量都需要加global,才能实现全局效果。

大家可能会疑问,我为什么要使用全局变量,作为线程通信的方式,为什么不用 信号槽?我最开始也是想用信号槽,也是这么做的,但是会报错。因为我这里使用线程的同时需要主界面发送图像数据给子线程,子线程处理完毕后,发送给主界面显示。也就是线程双向通信。

信号槽机制:子线程向主页面发送信号以及数据确实很方便。但是主界面发送数据给子线程就会出问题。使用信号槽程序会出现如下错误,获取不到信号的connect方法。

'PyQt5.QtCore.pyqtSignal' object has no attribute 'connect

上面的报错,我以前也遇到过,也有解决方案,但是这次问题和这个报错不沾边的....

信号的正确定义和使用以及上面报错解决方案:https://memory-qianxiao.blog.csdn.net/article/details/105754667

所以我就猜想信号槽是子线程向主界面发送信号使用的,如果要主界面发送数据给子线程,需要其他方法实现。 

线程类在主界面实例化与使用

这里贴出来的代码是我代码里面线程使用精简化后的,为了方便大家理解,同时写了注释,比较核心的几行代码就是init里面那几行。这个是科学使用PyQt线程的模板了。尤其注意线程完毕时,需要关闭线程, 线程不会自己关闭的。

class MainWindow(QtWidgets.QMainWindow): def __init__(self): # fixme PyQt线程科学用法 self.thread = QThread() # 实例化线程类 self.work_thread = workThread() #moveToThread方法把实例化线程移到Thread管理 self.work_thread.moveToThread(self.thread) # 线程开始执行之前,从相关线程发射信号 self.thread.started.connect(self.work_thread.work) #接收子线程信号发来的数据 self.work_thread.to_show_img_signal.connect(self.show_img_in_labelme) # 线程执行完成关闭线程 self.thread.finished.connect(self.threadStop) def threadStart(self): # 开启线程 self.thread.start() def threadStop(self): # 退出线程 self.thread.quit() #接收线程数据的槽函数 def show_img_in_labelme(self, qimage): self.onNewBrightnessContrast(qimage) 开启线程

我们这里需要特别注意,线程的开启应在主界面里面,当有需要处理耗时操作的时候,就主动开启一个线程,处理耗时任务,处理完毕,检测线程执行完毕,就需要关闭。上面已经定义了开启方法。所以只需要在用到的地方调用函数即可。

def eraser_or_brush(self, coordinate): #全局变量 global mask_list global imageData #业务逻辑代码 """橡皮擦功能""" """笔刷功能(逆向橡皮擦)""" #主动开启一个线程 self.thread.start() # 设置撤销按钮是否可用 self.actions.undo.setEnabled(self.isHasMaskImage()) 补充(信号的方式实现线程双向通信):

补充时间:20201年1月5日

上面的方式是基于全局变量,实现主线程和子线程的通信,子线程处理完毕向主线程发送信号。现在这里补充另一种写法思路:在主线程一开始(init初始化)就开启一个线程挂在后台,在有需要的的时候,就发送数据和信号,去让线程处理,处理完毕在发送信号给主线程。

线程类

与上面的线程类不同的是这里线程处理方法多了两个参数一个信号。

from qtpy.QtCore import QObject, QThread from qtpy import QtCore, QtGui import cv2 import numpy as np from . import utils import time class ImageProcessingThread(QObject): #处理完毕图像数据信号 to_show_img_signal = QtCore.Signal(QtGui.QImage) #线程接收参数信号 to_start_image_process_thread_signal = QtCore.Signal(bytes, list) def __init__(self): super(ImageProcessingThread, self).__init__() #接收两个个参数的方法 def work(self, imageData, mask_list): """图像处理业务过程""" #处理完毕发送信号 self.to_show_img_signal.emit(qimage) 线程实例化与开启线程挂在后台 class MainWindow(QtWidgets.QMainWindow): def __init__(self): # fixme PyQt线程科学用法 self.thread = QThread() # 初始化线程类传参 self.image_process_thread = ImageProcessingThread() self.image_process_thread.moveToThread(self.thread) # 连接槽函数 self.image_process_thread.to_start_image_process_thread_signal.connect(self.image_process_thread.work) self.image_process_thread.to_show_img_signal.connect(self.show_img_in_labelme) # 开启线程 一直挂在后台 self.thread.start() 发送信号,线程处理数据 def eraser_or_brush(self, coordinate): # 业务逻辑代码 """橡皮擦功能""" """笔刷功能(逆向橡皮擦)""" # 发送信号给线程,让线程开始工作 self.image_process_thread.to_start_image_process_thread_signal.emit(self.imageData, self.mask_list) # 设置撤销按钮是否可用 self.actions.undo.setEnabled(self.isHasMaskImage())

 

结语

希望我这篇文章对你有所帮助,希望三连,不胜感激~

多线程,多进程这一块其实还是挺难的,虽然代码很短,但是要具体实现某个复杂一点的功能,要控制好真的不的不容易~要花大量时间去采坑,去看别人的参考资料~而现在网上资料太多了,很多博主又是直接把转载或者把别人文章原样不动发表,让我们要花更多的时间去看找资料,真的很难受~

我的这篇文章主要是把我的心得体会与精简化的代码贴出来,供大家学习,相当于一个模版。大家少了采坑的过程,可能会对多线程理解还不够,不能理解我的一些做法,不过我会把我觉得有用参考文章给大家贴到后面。供大家去参考,理解。

参考文章

点击字体即可进入文章

使用PyQt线程的正确姿势pyqt5 的多线程(QThread)遇到的坑(一)PyQt5开发学习(三)--使用moveToThread异步刷新UIpyqt多线程moveToThread的使用

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 


【本文地址】

公司简介

联系我们

今日新闻

    推荐新闻

    专题文章
      CopyRight 2018-2019 实验室设备网 版权所有