PyQt5 执行耗时操作导致界面卡死或未响应的解决办法

这篇具有很好参考价值的文章主要介绍了PyQt5 执行耗时操作导致界面卡死或未响应的解决办法。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

问题场景:

当用PyQt5开发一个GUI界面 ,需要执行业务逻辑时,后台逻辑执行时间长,界面就容易出现卡死、未响应等问题。

问题原因:

在PyQt中,GUI界面本身就是一个处理事件循环的主线程,当进行耗时操作时,主线程GUI需要等待操作完成后才会响应,在等待这段时间,整个GUI就处于卡死的状态。在windows下,系统会认为这个程序运行出错了,会自动显示未响应,如果这时有其他的操作,整个程序就会卡死崩溃。

解决办法:

另开一个线程来执行这个耗时操作(使用QThread)

from PyQt5.QtCore import QThread

通过继承QThread并重写run()方法的方式实现多线程代码的编写。
结构大体如下:

class Worker(QThread):
    def __init__(self):
        super().__init__()
    def run(self):
        --snip--

把耗时操作放到一个Worker线程中的run()函数下执行,在GUI类文件中绑定操作的地方,创建Worker进程实例,启动进程即可。

t = Worker()
t.start()

进阶用法

上文中的方法开启了一个新的线程去完成耗时操作;在GUI界面运行的过程中,常需要与新的线程之间保持信息的传递即“通信”,在pyqt5中这通过信号去实现,这样能保证GUI界面对后台操作进行实时的响应,例如:按钮状态的更新、文字浏览窗口的消息变化、子窗口的打开及关闭等。

信号及自定义信号

在PyQt5中,信号与槽的使用有如下一些特点:
· 一个信号可以关联多个槽函数。
· 一个信号也可以关联其他信号。
· 信号的参数可以是任何Python数据类型。
· 一个槽函数可以和多个信号关联。
· 关联可以是直接的(同步)或排队的(异步)。
· 可以在不同线程之间建立关联。
· 信号与槽也可以断开关联。

在自定义类中还可以自定义信号。使用自定义信号在程序的对象之间传递信息是非常方便的,使用PyQt5.QtCore.pyqtSignal()为一个类定义新的信号。要自定义信号,类必须是QObject类的子类。pyqtSignal()的句法是:

        pyqtSignal(types[, name[, revision=0[, arguments=[]]]])

信号可以带有参数types,后面的参数都是一些可选项,基本不使用。信号需要定义为类属性,这样定义的信号是未绑定(unbound)信号。当创建类的实例后,PyQt5会自动将类的实例与信号绑定,这样就生成了绑定的(bound)信号。这与Python语言从类的函数生成绑定的方法的机制是一样的。一个绑定的信号(也就是类的实例对象的信号)具有connect()、disconnect()和emit()这3个函数,分别用于关联槽函数、断开与槽函数的关联、发射信号。

使用示例

通过信号的emit()函数发射信号。在类的某个状态发生变化,需要通知外部发生了这种变化时,发射相应的信号。如果信号关联了一个槽函数,就会执行槽函数,如果信号没有关联槽函数,就不会产生任何动作。

# -*- coding: utf-8 -*-

# Form implementation generated from reading ui file 'Qua_Ins.ui'
#
# Created by: PyQt5 UI code generator 5.15.4 @bill_love_3
import sys
import os
import time
from PyQt5 import QtCore, QtGui, QtWidgets
from PyQt5.QtCore import Qt, QThread, pyqtSignal, QObject
from PyQt5.QtWidgets import *
from PyQt5.QtGui import *


class Worker(QThread, QObject):  # 自定义信号,执行run()函数时,从线程发射此信号
    sinOut = pyqtSignal(str)
    sinEnd = pyqtSignal()

    def __init__(self, obj, parent=None):
        QThread.__init__(self, parent)
        QObject.__init__(self, parent)
        self.obj = obj

    def Threading_topo(self):  # CheckTopology子进程输出读取方法
         --snip-- # 耗时操作,约300S左右

    def run(self):
        self.sinOut.emit('...正在导入数据...')
        time.sleep(2)
        self.sinOut.emit('...开始检查...')
        time.sleep(1)
        myQIns = QI(self.obj.filename)
        myQIns.line_lsit.append(os.path.basename(self.obj.filename) + '\n')
        # ---------------------开启Check Topology子进程  ----------------------------------------------
        if self.obj.checkBox_Topology.isChecked():
            self.sinOut.emit('-----------------检查中----请等待程序完成请勿关闭----\n')
            self.Threading_topo()
        myQIns.Complete_check()
        self.sinOut.emit('检查完成。\n')
        time.sleep(2)
        myQIns.WriteTxt()
        self.sinOut.emit('报告生成中...\n')
        time.sleep(2)
        myQIns.CleanGdb()
        self.sinEnd.emit()

上面的片段就是一个完整的通过继承QThread, QObject类生成的自定义类,由它开启一个新的线程完成GUI程序中耗时的业务逻辑操作,保证GUI的主线程不会停下来等待,一直处于事件循环的状态。在GUI的类文件中,通过调用产生该Worker()类的实例,在需要进行该操作的信号绑定处开启线程。

class Ui_MainWindow():
    def __init__(self):
        self.filename = ''
        self.thread_btn_start = Worker(self, None)

这里将GUI的类创建的对象本身(self)作为obj参数传递到Worker()类中,可以看到Worker类中的self.obj.filename对应GUI类中的self.filename;通过这种用法可以实现GUI主线程对Worker开启的线程的信息传递,这种用法与类的嵌套中内部类和外部类之间通信的方法是一致的。

    def pushButton_start_event(self):  # 检查按钮绑定事件
        if self.filename:
            self.thread_btn_start.sinOut.connect(self.text_browser_show)
            self.thread_btn_start.sinEnd.connect(self.set_pushButton)
            self.thread_btn_start.start()

这里设置Worker线程中sinOut、sinEnd信号绑定的槽函数,之后开启线程。

注意

不要尝试在Worker开启的线程中去设置GUI界面中的控件属性,因为可能会导致未知的错误;pyqt最重要的特点信号和槽函数其中的一个用法就是用于各对象之间的信息传递,所以总是应该用信号去传递信息,包括对其他控件的状态修改等诸如此类的操作。

    def set_pushButton(self):
        self.pushButton_start.setEnabled(True)
        self.pushButton_chose.setEnabled(True)

这里通过thread_btn_start.sinEnd信号绑定的set_pushButton槽函数设置pushButton_start按钮的状态。

总结

通过自定义信号和继承重写QThread类的run()函数的方法可以很好的解决耗时操作导致界面卡死的问题。但也存在一些不足,比如增加额外的资源开销、程序整体执行时间变长了(变慢了);当然在更好的操作体验面前这些都是可以忽略的,毕竟GUI(Graphical User Interface)的定义就是图形用户界面。

其他补充

------------------------2023.05.10------------------更新--------------------------
应该将信号绑定放在需要多次操作的函数外部,否则应设置操作完成后的判断条件去断开信号绑定;不然会出现信号多次绑定到同一槽函数,结果就是一次信号传递执行两次已绑定槽函数。文章来源地址https://www.toymoban.com/news/detail-485407.html

class Ui_MainWindow(object):

    def __init__(self):
        self.filename = ''
        self.thread_btn_start = Worker(self, None)
        self.thread_btn_start.sinOut.connect(self.text_browser_show)
        self.thread_btn_start.sinEnd.connect(self.set_pushButton)

    def pushButton_start_event(self):  # 检查按钮绑定事件
        if self.filename:
            self.pushButton_start.setEnabled(False)
            self.pushButton_chose.setEnabled(False)
            self.thread_btn_start.start()
            
    def set_pushButton(self):
        self.pushButton_start.setEnabled(True)
        self.pushButton_chose.setEnabled(True)

到了这里,关于PyQt5 执行耗时操作导致界面卡死或未响应的解决办法的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • [pyqt5]关于在pyqt5界面上鼠标位置问题

    先上代码,主要看鼠标移动事件里面代码 通过运行代码可以发现: 上面这2行就是相当于屏幕左上角坐标了

    2024年02月15日
    浏览(24)
  • pyqt5界面自适应

    打开Qt Designer 创建一个窗体 假设界面控件需要这样的,想让这些控件跟着窗体任意大小  如果没有设计好这个控件之间的布局,直接将这个窗体创建布局(即在空白处右键点击布局选择符合的布局)会导致根据所选的布局直接把所有的控件移位。比如点击了垂直布局,就直接

    2024年02月11日
    浏览(25)
  • pyqt5优化美化界面代码

    自用的pyqt5界面优化美化代码,方便自己写界面时复制,也顺便造福广大网友吧! 首先安装基础工具: Pycharm配置QtDesigner(PyUIC、PyRcc ) 并懂得如何规范化创建qt界面: QtDesigner规范创建应用界面 文章将持续更新....... 鼠标不置于其上效果: 鼠标置于其上但不点击效果: 点击效

    2024年02月03日
    浏览(30)
  • PyQt5登录界面跳转

    目录 1、设计ui界面 2、设计逻辑代码,实现登录界面跳转 3、结果 1、设计ui界面 设计后的ui界面 在这里可以设置密码不显示 这里可以设置快捷键 最后将ui界面转为py文件后获得的逻辑代码为:(文件名为 Login.py ) 2、设计逻辑代码,实现登录界面跳转 这里登录后,我们转到

    2024年02月12日
    浏览(29)
  • 界面开发(2)--- 使用PyQt5制作用户登陆界面

    上篇文章已经介绍了如何配置PyQt5环境,这篇文章在此基础上展开,主要记录一下如何使用 PyQt5 制作用户登陆界面,并对一些基础操作进行介绍。 下面是具体步骤,一起来看看吧! 1. 打开 Pycharm 中的 Qt Designer 工具。 2. 选择Main Window模式,创建界面窗口。 3. 移除菜单栏和状态

    2024年02月05日
    浏览(41)
  • 界面开发(1) --- PyQt5环境配置

    第一步:首先安装社区版Pycharm 下载地址:https://www.jetbrains.com/pycharm/download/#section=windows 第二步:安装Anaconda3,配置虚拟环境 下载地址:https://www.anaconda.com/ 第三步:安装 Python 第三方库,包括 pyqt5,pyqt5-tools 和 pyqt5designer 在cmd中使用 pip install pyqt5 , pip install pyqt5-tools , pip in

    2024年04月12日
    浏览(35)
  • 使用PYQT5设计登录界面并实现界面跳转

    目录   1 UI登录界面的布局 2 UI登录界面布局对应的代码 3 登录界面和界面跳转完整代码 4 跳转界面代码函数和优化界面代码 5 最终效果        其中, 欢迎使用 XXXX 软件管理员密码 使用的是左边功能的 label 类、 登录 使用的是左边功能的 Push Button 类、 管理员和密码的输入

    2024年02月02日
    浏览(37)
  • 【pyqt5界面化开发-5】网格布局(九宫格)界面

    需要模块:QGridLayout 代码

    2024年02月11日
    浏览(24)
  • 界面开发(3)--- PyQt5用户登录界面连接数据库

    为了实现用户登录界面的登录功能,我们必须建立一个数据库,并把账号和对应的密码,存储到数据库中。如果输入的账号和密码与数据库中的一致,那我们就允许用户登录,进入新的界面。 上篇文章介绍了如何使用PyQt5制作用户登录界面,这篇文章在此基础上展开,建立简

    2024年02月05日
    浏览(34)
  • 【pyqt5界面化工具开发-11】界面化显示检测信息

    目录 0x00 前言: 一、布局的设置 二、消息的显示 我们在10讲的基础上,需要将其输出到界面上 思路: 1、消息的传递 2、布局的设置 先考虑好消息的传递,再来完善布局 其实先完善布局,再来设置消息的传递也行 (我不是中国人,就是外国人) 将布局设置好以后,我们就

    2024年02月10日
    浏览(31)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包