PyQt - 使用多线程避免界面卡顿

news2024/9/21 14:34:26

PYQT作为界面程序包,为Pythoner快速构建界面,提供了便利性。特别是结合Pycharm扩展工具(QTdesigner)能够通过“拖拖拽拽”的方式构建简单界面。通过UIC将UI文件快速转化为PY文件,节省了时间。

  PYQT的项目实践,必须参照MVC模式,才能形成多人工作合力,关于MVC的实践,会另起它文进行总结归纳,本文重点总结归纳PYQT项目中,因耗时操作产生界面卡顿现象时,如何通过分线程的方式,解决主界面线程等待造成界面卡顿的问题。

  一、问题的出现

  设计了一个界面,去局域网共享文件夹中查看是否存在文件,若存在,则在textBrowser中显示文件数量,每隔几秒钟执行一次,同时将信息Append到textBrowser中。

  使用os.listdir(target_path)打开共享文件夹时,由于网络故障,待反馈耗时大,由于Ui_MainWindow中的方法都在主线程中进行,造成界面的卡顿。

  二、解决思路

  将耗时操作分离至新线程,主界面保留资源用于交互。查询网络上现有的解决思路,大致如下:利用PYQT中的QThread类,新起一个线程,用来处理共享文件夹查阅的操作,待该线程处理完成后,主线程接收子线程处理结果,append到textBrowser中。

  三、难点

  利用信号与槽的机制,将主、子线程建立关联关系。

  四、如何实现

  1、实例化Ui_MainWindow类的MyThread对象,用于填装子线程处理任务。

  self.my_thread = MyThread()

  2、将事件信号与槽函数建立联系,通过count函数启用线程。

  self.StopButton.clicked.connect(self.CountFilesNum)

  3、Count函数:

  self.textBrowser_2.append("开始检测……")
  self.my_thread.is_on = True
  self.my_thread.start()#启动线程

  4、设计MyThread类,继承Qthread类

  my_signal = pyqtSignal(str)  #

  tipwords = ''
  def __init__(self):
      super(MyThread, self).__init__()
      self.tipwords = ''
      self.is_on = True

  定义一个信号(my_signal)用于向槽函数传递字符型变量(字符会输出到textBrowser中),调用父类的INIT方法,完成初始化设置。

  5、定义线程中的主逻辑函数:

  all_content = os.listdir(target_path)

  ……

  self.my_signal.emit(str(self.tipwords))  

  函数执行完,释放信号。线程中的信号关联到append函数

  self.my_thread.my_signal.connect(self.appendTextBrowser)  

  实现了线程中数据返回主线程的目的。

  五、总结

  除了button中的信号与槽机制外,主、子线程也是通过信号与槽机制完成的关联,可见,信号与槽机制,确实是PYQT的核心。
      在使用pyqt开发界面时,遇到了一种情况,就是在点击按钮之后,响应函数中会启动一个循环,该循环会一直执行,然后就造成界面无响应,如下所示,由于我是在Linux下运行的,所以界面直接显示成灰色(windows应该显示“无响应”):

这是因为对于pyqt来说,界面线程是主线程,如果我们在主线程函数里面调用了一个耗时比较久的循环,可能就会造成主界面线程卡死在循环中,从而造成无法操作主界面或者主界面卡顿、卡死。

所以这种情况下必须使用多线程的方式来解决,即在主界面线程中在启动一个新的子线程,利用该子线程处理比较耗时的操作,然后通过signal-slot机制将子线程的数据反馈到主界面线程中,而且在子线程中不能操作界面。这就是所说的:UI只用来操作UI,子线程只用来处理数据,就是将UI的操作与耗时数据的处理进行分开处理。

在pyqt中,可以通过QThread建立一个线程,

2、使用多线程解决界面卡顿 - 方式1
下面介绍 QThread 的第一种用法:新建一个类 RunThread 继承自 QThread,然后在 RunThread 类中重写 run() 函数,在 run() 函数中进行耗时数据的处理。下面是它的用法:


#!/usr/bin/python
# coding:UTF-8
from PyQt5 import QtWidgets, QtCore
import sys
from PyQt5.QtCore import *
import time


# 继承QThread
class Runthread(QtCore.QThread):
    #  通过类成员对象定义信号对象
    _signal = pyqtSignal(str)
 
    def __init__(self):
        super(Runthread, self).__init__()
 
    def __del__(self):
        self.wait()
 
    def run(self):
 
        for i in range(100):
            time.sleep(0.1)
            self._signal.emit(str(i))  # 注意这里与_signal = pyqtSignal(str)中的类型相同


class Example(QtWidgets.QWidget):
    def __init__(self):
        super(Example, self).__init__()
        # 按钮初始化
        self.button = QtWidgets.QPushButton('开始', self)
        self.button.move(120, 80)
        self.button.clicked.connect(self.start_login)  # 绑定多线程触发事件
 
        # 进度条设置
        self.pbar = QtWidgets.QProgressBar(self)
        self.pbar.setGeometry(50, 50, 210, 25)
        self.pbar.setValue(0)
 
        # 窗口初始化
        self.setGeometry(300, 300, 300, 200)
        self.show()
 
        self.thread = None  # 初始化线程
 
    def start_login(self):
        # 创建线程
        self.thread = Runthread()
        # 连接信号
        self.thread._signal.connect(self.call_backlog)  # 进程连接回传到GUI的事件
        # 开始线程
        self.thread.start()
 
    def call_backlog(self, msg):
        self.pbar.setValue(int(msg))  # 将线程的参数传入进度条
 
 
if __name__ == "__main__":
    app = QtWidgets.QApplication(sys.argv)
    myshow = Example()
    myshow.show()
    sys.exit(app.exec_())

 上面的代码建立一个界面,界面中只包含了一个进度条和一个按钮,点击按钮之后,进度条开始运行。

在上面的代码中,新建了一个 RunThread 类,该类继承自 QThread 类,在 RunThread 中重写了 run() 函数,并将耗时处理放在了 run() 函数中,点击按钮之后,触发 start_login() 函数,在start_login() 中,先创建了 RunThread 线程类的对象,然后将该类中的 _signal 信号与 Example 类中的 call_back() 函数建立连接,这样,就可以在run()函数运行时,将运行时的数据传递(异步,因为信号的传递与触发有一定的延时)到主机面 Example 类中并进行显示,如下所示:

3、使用多线程解决界面卡顿 - 方式2
在pyqt中多线程的使用还有另外一种方式:RunThread 类继承自 QObject,而非继承自 QThread。这种方式使用起来比第一种要复杂,但是这种方法将数据的处理与线程的创建与启动分开进行处理,在某些场景下,采用这种方式会比较方便。

下面是第二种方式的代码:

#!/usr/bin/python
# coding:UTF-8
from PyQt5 import QtWidgets, QtCore
import sys
from PyQt5.QtCore import *
import time
 
 
# 继承 QObject
class Runthread(QtCore.QObject):
    #  通过类成员对象定义信号对象
    signal = pyqtSignal(str)
 
    def __init__(self):
        super(Runthread, self).__init__()
        self.flag = True
 
    def __del__(self):
        print(">>> __del__")
 
    def run(self):
        i = 0
        while self.flag:
            time.sleep(1)
            if i <= 100:
                self.signal.emit(str(i))  # 注意这里与_signal = pyqtSignal(str)中的类型相同
                i += 1
        print(">>> run end: ")
 
 
class Example(QtWidgets.QWidget):
    #  通过类成员对象定义信号对象
    _startThread = pyqtSignal()
 
    def __init__(self):
        super(Example, self).__init__()
        # 按钮初始化
        self.button_start = QtWidgets.QPushButton('开始', self)
        self.button_stop = QtWidgets.QPushButton('停止', self)
        self.button_start.move(60, 80)
        self.button_stop.move(160, 80)
        self.button_start.clicked.connect(self.start)  # 绑定多线程触发事件
        self.button_stop.clicked.connect(self.stop)  # 绑定多线程触发事件
 
        # 进度条设置
        self.pbar = QtWidgets.QProgressBar(self)
        self.pbar.setGeometry(50, 50, 210, 25)
        self.pbar.setValue(0)
 
        # 窗口初始化
        self.setGeometry(300, 300, 300, 200)
        self.show()
 
        self.myT = Runthread()          # 创建线程对象
        self.thread = QThread(self)     # 初始化QThread子线程
 
        # 把自定义线程加入到QThread子线程中
        self.myT.moveToThread(self.thread)
 
        self._startThread.connect(self.myT.run)     # 只能通过信号-槽启动线程处理函数
        self.myT.signal.connect(self.call_backlog)
 
    def start(self):
        if self.thread.isRunning():     # 如果该线程正在运行,则不再重新启动
            return
 
        # 先启动QThread子线程
        self.myT.flag = True
        self.thread.start()
        # 发送信号,启动线程处理函数
        # 不能直接调用,否则会导致线程处理函数和主线程是在同一个线程,同样操作不了主界面
        self._startThread.emit()
 
    def stop(self):
        if not self.thread.isRunning():     # 如果该线程已经结束,则不再重新关闭
            return
        self.myT.flag = False
        self.stop_thread()
 
    def call_backlog(self, msg):
        self.pbar.setValue(int(msg))  # 将线程的参数传入进度条
 
    def stop_thread(self):
        print(">>> stop_thread... ")
        if not self.thread.isRunning():
            return
        self.thread.quit()      # 退出
        self.thread.wait()      # 回收资源
        print(">>> stop_thread end... ")
 
 
if __name__ == "__main__":
    app = QtWidgets.QApplication(sys.argv)
    myshow = Example()
    myshow.show()
    sys.exit(app.exec_())
 该界面包含了一个进度条、一个开始按钮、一个停止按钮。当点击“开始”按钮之后,进度条会开始运行;当点击“停止”按钮时,进度条会停止运行,如下所示:

其中 RunThread 类是线程处理函数类,该类继承自 QObject,然后通过 moveToThread 函数将该线程处理函数类添加进一个线程中。

在使用这种方式时需要注意一下几点:

self.myT=Runthread();  // 创建线程处理函数类对象,需要注意的是创建时不能指定父对象,要不然moveToThread函数会报错
self._startThread.emit();  // 虽然说,将myT添加进thread线程,并且调用thread.start(),但是,并不能通过直接调用的方式来调用RunThread类中的线程处理函数run(),如果直接调用的话,相当于还是主界面线程在调用,依然会造成主界面卡顿。此时应该使用signal-slot的方式进行调用,即在Example类中声明一个信号_startThread,并通过self._startThread.connect(self.myT.run) 将该信号与 RunThread 类中的线程处理函数建立连接,这样当调用 thread.start() 后,再调用 self._startThread.emit() 函数就可以调用 run() 函数了
self.myT.signal.connect(self.call_backlog);   // 为了获取RunThread类中的处理数据,也只能通过signal-slot的方式进行获取;
当thread调用quit()和wait()函数之后,此时该线程已经停止运行,但是线程处理函数run()还未停止运行,所以,需要在run函数中的循环中添加一个判断标志位,当thread线程停止后,将该标志位置为False,这样退出循环之后run函数就退出了;
当run函数退出之后,此时RunThread类对象myT并没有销毁,因为它是Example类的成员,所以只有当Example销毁时,myT才会销毁;
4、关于connect的连接方式
在QT中,查看connect函数原型:

当然,connect有多重函数重载形式,以上只是其中的一种。其中的第五个参数type指明了signal-slot的连接方式,Qt::ConnectionType有一些几种类型:

前面三种是比较常用的,其中QueuedConnection方式是用在上面多线程的情况下。

QueuedConnection:槽函数所在线程和接收所在线程是一样的;

DirectConnection: 槽函数所在线程和发送者所在线程是一样的;

不过大多数情况下,调用connect是使用默认参数就可以了,当使用默认参数AutoConnection时:

在多线程情况下,默认使用QueuedConnection;

在单线程下,默认使用DirectConnection;

同理,在pyqt中也一样,pyqt中connect函数原型:

使用方式也一样,直接使用默认连接方式就可以了。

其实pyqt和qt差别不大,就只有语言上的差别,使用方式还都是一样的,我一般都是先查qt上资料然后在套用到pyqt上。
 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/80061.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

痞子衡嵌入式:我被邀请做贸泽电子与非网联合推出的《对话工程师》节目嘉宾...

《对话工程师》是「贸泽电子」赞助、「与非网」制作的一档网络节目&#xff0c;自2022年11月起&#xff0c;邀请不同技术领域的资深工程师&#xff0c;聊聊开发过程中的经验感悟&#xff0c;栏目共 10 期&#xff0c;痞子衡有幸被邀请做了第 4 期节目的嘉宾(12月5日在 「B站 - …

COVID

不信谣不传谣&#xff0c;只是自己的一点记录&#xff0c;有引用到各位大佬的内容&#xff0c;侵删&#xff0c;感谢感谢&#xff0c;对自己可以理中客&#xff0c;对别人还是尽可能主观上的友好&#xff0c;不要慷他人之慨。 准备 喉咙刺痛方面&#xff1a;柠檬&#xff08;…

Python爬虫实战,requests+openpyxl模块,爬取手机商品信息数据(附源码)

前言 今天给大家介绍的是Python爬取手机商品信息数据&#xff0c;在这里给需要的小伙伴们代码&#xff0c;并且给出一点小心得。 首先是爬取之前应该尽可能伪装成浏览器而不被识别出来是爬虫&#xff0c;基本的是加请求头&#xff0c;但是这样的纯文本数据爬取的人会很多&…

读《冯诺伊曼传》

关于冯诺依曼几年前读了本冯诺依曼的书&#xff0c;冯诺依曼是20世纪的全才&#xff0c;原名约翰尼&#xff0c;匈牙利美籍科学家&#xff0c;被称为计算机之父和博弈论之父。计算机和博弈论都深刻改变人类的生活工作和思维方式&#xff0c;极大地促进了社会和人类文明的进步发…

CMake中add_definitions/add_compile_definitions的使用

CMake中的add_definitions命令用于在源文件的编译中添加-D定义标志&#xff0c;其格式如下&#xff1a; add_definitions(-DFOO -DBAR ...) 将当前目录中的target的定义添加到编译器命令行(compiler command line)中,无论是在调用此命令之前还是之后添加的,还是之后添加的子目…

C/C++入门004-C语言流程控制

文章目录流程控制顺序结构:选择结构if选择结构switch选择循环结构while循环do whilefor循环循环优化四大跳转break:continuegotoreturn案例参考&#xff1a;https://blog.csdn.net/weixin_44617968/article/details/117656810 流程控制 控制流程&#xff08;也称为流程控制&a…

HC-05蓝牙模块AT指令 ERROR问题

AT模式指令识别貌似有些问题,但不知道什么原因导致的&#xff0c;返回正常值后&#xff0c;一切通信正常。 查了一些资料&#xff0c;觉得下面两个最靠谱。 1.引脚电平问题 2.需要3.3v接EN引脚 等到故障再次发生&#xff0c;尝试EN接3.3V&#xff0c;数据收发一切正常。 结论&…

acwing基础课——bellman-ford

由数据范围反推算法复杂度以及算法内容 - AcWing 常用代码模板3——搜索与图论 - AcWing 基本思想&#xff1a; 逐遍的对图中每一个边去迭代计算起始点到其余各点的最短路径&#xff0c;执行n-1遍&#xff0c;最终得到起始点到其余各点的最短路径。&#xff08;n为连通图结点数…

CEF使用 libcef_dll_wrapper Debug版链接报错

编译后使用Release版编译链接运行正常&#xff0c;而Debug版报错如下&#xff1a; libcef_dll_wrapper.lib(cef_logging.obj) : error LNK2038: 检测到“_ITERATOR_DEBUG_LEVEL”的不匹配项: 值“0”不匹配值“2”(CefAppEx.obj 中)具体原因是Debug版使用了Release版的库&…

图形化跟踪个股RPS走势,挖掘出源源不断的牛股!股票量化分析工具QTYX-V2.5.7...

概述RPS选股策略威廉欧奈尔把投资理念集中于他自创的CANSLIM选股系统&#xff0c;凭借着这个系统驰骋股票市场数十年&#xff0c;无论在牛市还是熊市&#xff0c;这个系统都是最稳定、表现最好的系统之一。CANSLIM选股系统中有一个RPS指标&#xff08;Relative Price Strength …

华为eNSP模拟器配置VRRP网关冗余

作用&#xff1a; 提供网关冗余功能&#xff0c;保证出口网关的高可用型。当网关发生故障的时候&#xff0c;能让PC快速的切换。 概念&#xff1a; 通过VRRP协议创建出一个虚拟网关&#xff0c;主用路由器down之后备用路由器能马上接替其主的位置&#xff0c;继续提供出口网…

【C/C++】斐波那契数列数列系列问题详解

&#x1f34e; 博客主页&#xff1a;&#x1f319;披星戴月的贾维斯 &#x1f34e; 欢迎关注&#xff1a;&#x1f44d;点赞&#x1f343;收藏&#x1f525;留言 &#x1f347;系列专栏&#xff1a;&#x1f319; C初阶 &#x1f319;励志卓越可以成为你努力的动力&#xff0c;…

1.JavaScript简介

**JavaScript ** 是什么&#xff1f;&#xff08;重点&#xff09; Js是一种专门为网页交互设计的客户端&#xff08;浏览器端&#xff09;的脚本语言&#xff1b; Js与html和css有相似之处&#xff0c;都在浏览器端解析&#xff1b; Js和java,c#,php等一样&#xff0c;是一…

Spring 通过 @Lazy 注解解决构造方法循环依赖问题

什么是循环依赖? 先定义两个类 Apple、Orange&#xff0c;如下所示&#xff1a; Component public class Apple{Autowiredprivate Orange orange; }Component public class Orange {Autowiredprivate Apple apple; }像这种在 Apple 里面有一个属性 Orange、Orange 中有一个属…

k8s 驱逐eviction机制源码分析

原理部分 1. 驱逐概念介绍 kubelet会定期监控node的内存&#xff0c;磁盘&#xff0c;文件系统等资源&#xff0c;当达到指定的阈值后&#xff0c;就会先尝试回收node级别的资源&#xff0c;比如当磁盘资源不足时会删除不同的image&#xff0c;如果仍然在阈值之上就会开始驱逐…

森林图

森林图 以统计指标和统计分析方法为基础&#xff0c;用数值计算绘制出的图形&#xff0c;通常是在平面直角坐标系中&#xff0c;以一条垂直的无效 线&#xff08;0或者1&#xff09;为中心&#xff0c;用平衡于x轴的多条线段描述每个组指标的中值和可信区间&#xff0c;最后一…

星空华文通过聆讯:吃《中国好声音》老本 华人文化是股东

雷递网 雷建平 12月9日星空华文控股有限公司&#xff08;简称&#xff1a;“星空华文”&#xff09;今日通过聆讯&#xff0c;准备在香港上市。星空华文的前身是上海灿星文化传媒股份有限公司&#xff08;简称“灿星文化”&#xff09;。2018年2月&#xff0c;灿星文化向上海证…

Web端H5播放RTSP

Web端H5播放RTSP一、要实现二、基础介绍1.RTSP是什么&#xff1f;2.RTSP播放测试工具VLC3.主流设备常用的RTSP格式三、方案1. webrtc-streamer2. 安装和配置环境3. 运行demo.html4.存疑5.参考了好多~一、要实现 不用萤石云等类似的平台&#xff0c;实现Web端直接显示监控视频。…

LwIP——以太网描述符

目录 什么是以太网DMA描述符 TX DMA描述符成员变量简介 RX DMA描述符成员变量简介 以太网DMA描述符结构 如何追踪描述符 如何创建Tx/Rx描述符 以太网发送和接收数据流程 总结 在移植LwIP之前有必要了解一下以太网DMA描述符的相关知识&#xff0c;ST以太网模块中的接收/发…

【华为OD机试真题 python】箱子之字形摆放【2022 Q4 | 100分】

■ 题目描述 【箱子之字形摆放】 有一批箱子(形式为字符串,设为str), 要求将这批箱子按从上到下以之字形的顺序摆放在宽度为 n 的空地,请输出箱子的摆放位置。 例如:箱子ABCDEFG,空地宽度为3,摆放结果如图: 则输出结果为:AFGBECD 输入描述 输入一行字符串,通过空…