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

 更新时间:2023年12月11日 16:19:51   作者:bill_love_c  
这篇文章主要给大家介绍了关于PyQt5执行耗时操作导致界面卡死或未响应的原因及解决办法,由于耗时的操作会独占系统cpu资源,让界面卡死在那里,文中通过代码介绍的非常详细,需要的朋友可以参考下

问题场景:

当用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)的定义就是图形用户界面。

其他补充

应该将信号绑定放在需要多次操作的函数外部,否则应设置操作完成后的判断条件去断开信号绑定;不然会出现信号多次绑定到同一槽函数,结果就是一次信号传递执行两次已绑定槽函数。

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执行耗时操作导致界面卡死或未响应的原因及解决办法的文章就介绍到这了,更多相关PyQt5执行耗操作导致界面卡死内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • Python使用Streamlit快速创建仪表盘

    Python使用Streamlit快速创建仪表盘

    这篇文章主要为大家详细介绍了Python如何使用Streamlit快速创建一个简单的仪表盘,文中的示例代码简洁易懂,快跟随小编一起来学习一下吧
    2023-09-09
  • python基础之共有操作

    python基础之共有操作

    这篇文章主要介绍了python函数的定义和调用,实例分析了Python中返回一个返回值与多个返回值的方法,需要的朋友可以参考下
    2021-10-10
  • pandas检查和填充缺失值的N种方法总结

    pandas检查和填充缺失值的N种方法总结

    本文主要介绍了pandas检查和填充缺失值的N种方法总结,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2023-01-01
  • Python 如何批量更新已安装的库

    Python 如何批量更新已安装的库

    这篇文章主要介绍了Python 如何批量更新已安装的库,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2020-05-05
  • pytorch __init__、forward与__call__的用法小结

    pytorch __init__、forward与__call__的用法小结

    这篇文章主要介绍了pytorch __init__、forward与__call__的用法小结,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2021-02-02
  • python从子线程中获得返回值的方法

    python从子线程中获得返回值的方法

    今天小编就为大家分享一篇python从子线程中获得返回值的方法,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2019-01-01
  • Python使用树状图实现可视化聚类详解

    Python使用树状图实现可视化聚类详解

    一般情况下,我们都是使用散点图进行聚类可视化,但是某些的聚类算法可视化时散点图并不理想,所以在这篇文章中,我们介绍如何使用树状图(Dendrograms)对我们的聚类结果进行可视化
    2023-03-03
  • python数据可视化JupyterLab实用扩展程序Mito

    python数据可视化JupyterLab实用扩展程序Mito

    这篇文章主要为大家介绍了python数据可视化JupyterLab实用扩展程序Mito的功能应用示例详解,有需要的朋友可以借鉴参考下,希望能够有所帮助
    2021-11-11
  • Python如何读取相对路径文件

    Python如何读取相对路径文件

    这篇文章主要介绍了Python如何读取相对路径文件问题,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2023-03-03
  • python使用post提交数据到远程url的方法

    python使用post提交数据到远程url的方法

    这篇文章主要介绍了python使用post提交数据到远程url的方法,涉及Python使用post传递数据的相关技巧,需要的朋友可以参考下
    2015-04-04

最新评论