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

目录

前言

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

 

结语

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

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

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

参考文章

点击字体即可进入文章

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 
  • 46
    点赞
  • 119
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 15
    评论
PyQt5是一个强大的Python GUI框架,可以用于创建多窗口应用程序。同时,它也提供了多线程的支持,可以在应用程序中同时执行多个任务。 对于多窗口应用程序,可以使用PyQt5中的QMainWindow类创建主窗口,并使用其他QWidget类创建额外的子窗口。主窗口是整个应用程序的主要界面,而子窗口可以用于显示其他视图或执行其他任务使用多线程可以使应用程序具有更好的响应性能,特别是在处理复杂的或耗时任务时。可以使用PyQt5中的QThread类创建线程,然后将任务分配给这些线程进行并行执行,以避免主线程被阻塞。 在多窗口应用程序中使用多线程时,需要注意以下几点。首先,应该避免使用全局变量,而应该使用线程通信机制进行数据传递。PyQt5提供了信号与槽机制和队列等线程通信的方式。 其次,需要注意线程安全性。多个线程共享相同的数据时,可能会引起数据竞争和不一致性。可以使用互斥锁等同步机制来保证数据的一致性。 此外,还需要注意界面的更新。在多线程中进行的任务会影响到界面的显示,但需要注意将界面的更新操作放在主线程中进行,以避免多个线程同时更新界面而导致的冲突。 总之,PyQt5提供了多窗口和多线程的支持,可以帮助我们创建功能强大的应用程序,提升用户体验和性能。但在使用过程中,需要注意线程通信线程安全性和界面更新等问题,以保证应用程序的稳定和可靠性。
评论 15
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

落凡尘.

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

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

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

打赏作者

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

抵扣说明:

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

余额充值