PyQt5執(zhí)行耗時(shí)操作導(dǎo)致界面卡死或未響應(yīng)的原因及解決辦法
問(wèn)題場(chǎng)景:
當(dāng)用PyQt5開(kāi)發(fā)一個(gè)GUI界面 ,需要執(zhí)行業(yè)務(wù)邏輯時(shí),后臺(tái)邏輯執(zhí)行時(shí)間長(zhǎng),界面就容易出現(xiàn)卡死、未響應(yīng)等問(wèn)題。
問(wèn)題原因:
在PyQt中,GUI界面本身就是一個(gè)處理事件循環(huán)的主線程,當(dāng)進(jìn)行耗時(shí)操作時(shí),主線程GUI需要等待操作完成后才會(huì)響應(yīng),在等待這段時(shí)間,整個(gè)GUI就處于卡死的狀態(tài)。在windows下,系統(tǒng)會(huì)認(rèn)為這個(gè)程序運(yùn)行出錯(cuò)了,會(huì)自動(dòng)顯示未響應(yīng),如果這時(shí)有其他的操作,整個(gè)程序就會(huì)卡死崩潰。
解決辦法:
另開(kāi)一個(gè)線程來(lái)執(zhí)行這個(gè)耗時(shí)操作(使用QThread)
from PyQt5.QtCore import QThread
通過(guò)繼承QThread并重寫(xiě)run()方法的方式實(shí)現(xiàn)多線程代碼的編寫(xiě)。
結(jié)構(gòu)大體如下:
class Worker(QThread):
def __init__(self):
super().__init__()
def run(self):
--snip--
把耗時(shí)操作放到一個(gè)Worker線程中的run()函數(shù)下執(zhí)行,在GUI類(lèi)文件中綁定操作的地方,創(chuàng)建Worker進(jìn)程實(shí)例,啟動(dòng)進(jìn)程即可。
t = Worker() t.start()
進(jìn)階用法
上文中的方法開(kāi)啟了一個(gè)新的線程去完成耗時(shí)操作;在GUI界面運(yùn)行的過(guò)程中,常需要與新的線程之間保持信息的傳遞即“通信”,在pyqt5中這通過(guò)信號(hào)去實(shí)現(xiàn),這樣能保證GUI界面對(duì)后臺(tái)操作進(jìn)行實(shí)時(shí)的響應(yīng),例如:按鈕狀態(tài)的更新、文字瀏覽窗口的消息變化、子窗口的打開(kāi)及關(guān)閉等。
信號(hào)及自定義信號(hào)
在PyQt5中,信號(hào)與槽的使用有如下一些特點(diǎn):
· 一個(gè)信號(hào)可以關(guān)聯(lián)多個(gè)槽函數(shù)。
· 一個(gè)信號(hào)也可以關(guān)聯(lián)其他信號(hào)。
· 信號(hào)的參數(shù)可以是任何Python數(shù)據(jù)類(lèi)型。
· 一個(gè)槽函數(shù)可以和多個(gè)信號(hào)關(guān)聯(lián)。
· 關(guān)聯(lián)可以是直接的(同步)或排隊(duì)的(異步)。
· 可以在不同線程之間建立關(guān)聯(lián)。
· 信號(hào)與槽也可以斷開(kāi)關(guān)聯(lián)。
在自定義類(lèi)中還可以自定義信號(hào)。使用自定義信號(hào)在程序的對(duì)象之間傳遞信息是非常方便的,使用PyQt5.QtCore.pyqtSignal()為一個(gè)類(lèi)定義新的信號(hào)。要自定義信號(hào),類(lèi)必須是QObject類(lèi)的子類(lèi)。pyqtSignal()的句法是:
pyqtSignal(types[, name[, revision=0[, arguments=[]]]])
信號(hào)可以帶有參數(shù)types,后面的參數(shù)都是一些可選項(xiàng),基本不使用。信號(hào)需要定義為類(lèi)屬性,這樣定義的信號(hào)是未綁定(unbound)信號(hào)。當(dāng)創(chuàng)建類(lèi)的實(shí)例后,PyQt5會(huì)自動(dòng)將類(lèi)的實(shí)例與信號(hào)綁定,這樣就生成了綁定的(bound)信號(hào)。這與Python語(yǔ)言從類(lèi)的函數(shù)生成綁定的方法的機(jī)制是一樣的。一個(gè)綁定的信號(hào)(也就是類(lèi)的實(shí)例對(duì)象的信號(hào))具有connect()、disconnect()和emit()這3個(gè)函數(shù),分別用于關(guān)聯(lián)槽函數(shù)、斷開(kāi)與槽函數(shù)的關(guān)聯(lián)、發(fā)射信號(hào)。
使用示例
通過(guò)信號(hào)的emit()函數(shù)發(fā)射信號(hào)。在類(lèi)的某個(gè)狀態(tài)發(fā)生變化,需要通知外部發(fā)生了這種變化時(shí),發(fā)射相應(yīng)的信號(hào)。如果信號(hào)關(guān)聯(lián)了一個(gè)槽函數(shù),就會(huì)執(zhí)行槽函數(shù),如果信號(hào)沒(méi)有關(guān)聯(lián)槽函數(shù),就不會(huì)產(chǎn)生任何動(dòng)作。
# -*- 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): # 自定義信號(hào),執(zhí)行run()函數(shù)時(shí),從線程發(fā)射此信號(hào)
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子進(jìn)程輸出讀取方法
--snip-- # 耗時(shí)操作,約300S左右
def run(self):
self.sinOut.emit('...正在導(dǎo)入數(shù)據(jù)...')
time.sleep(2)
self.sinOut.emit('...開(kāi)始檢查...')
time.sleep(1)
myQIns = QI(self.obj.filename)
myQIns.line_lsit.append(os.path.basename(self.obj.filename) + '\n')
# ---------------------開(kāi)啟Check Topology子進(jìn)程 ----------------------------------------------
if self.obj.checkBox_Topology.isChecked():
self.sinOut.emit('-----------------檢查中----請(qǐng)等待程序完成請(qǐng)勿關(guān)閉----\n')
self.Threading_topo()
myQIns.Complete_check()
self.sinOut.emit('檢查完成。\n')
time.sleep(2)
myQIns.WriteTxt()
self.sinOut.emit('報(bào)告生成中...\n')
time.sleep(2)
myQIns.CleanGdb()
self.sinEnd.emit()
上面的片段就是一個(gè)完整的通過(guò)繼承QThread, QObject類(lèi)生成的自定義類(lèi),由它開(kāi)啟一個(gè)新的線程完成GUI程序中耗時(shí)的業(yè)務(wù)邏輯操作,保證GUI的主線程不會(huì)停下來(lái)等待,一直處于事件循環(huán)的狀態(tài)。在GUI的類(lèi)文件中,通過(guò)調(diào)用產(chǎn)生該Worker()類(lèi)的實(shí)例,在需要進(jìn)行該操作的信號(hào)綁定處開(kāi)啟線程。
class Ui_MainWindow():
def __init__(self):
self.filename = ''
self.thread_btn_start = Worker(self, None)
這里將GUI的類(lèi)創(chuàng)建的對(duì)象本身(self)作為obj參數(shù)傳遞到Worker()類(lèi)中,可以看到Worker類(lèi)中的self.obj.filename對(duì)應(yīng)GUI類(lèi)中的self.filename;通過(guò)這種用法可以實(shí)現(xiàn)GUI主線程對(duì)Worker開(kāi)啟的線程的信息傳遞,這種用法與類(lèi)的嵌套中內(nèi)部類(lèi)和外部類(lèi)之間通信的方法是一致的。
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()
這里設(shè)置Worker線程中sinOut、sinEnd信號(hào)綁定的槽函數(shù),之后開(kāi)啟線程。
注意
不要嘗試在Worker開(kāi)啟的線程中去設(shè)置GUI界面中的控件屬性,因?yàn)榭赡軙?huì)導(dǎo)致未知的錯(cuò)誤;pyqt最重要的特點(diǎn)信號(hào)和槽函數(shù)其中的一個(gè)用法就是用于各對(duì)象之間的信息傳遞,所以總是應(yīng)該用信號(hào)去傳遞信息,包括對(duì)其他控件的狀態(tài)修改等諸如此類(lèi)的操作。
def set_pushButton(self):
self.pushButton_start.setEnabled(True)
self.pushButton_chose.setEnabled(True)
這里通過(guò)thread_btn_start.sinEnd信號(hào)綁定的set_pushButton槽函數(shù)設(shè)置pushButton_start按鈕的狀態(tài)。
總結(jié)
通過(guò)自定義信號(hào)和繼承重寫(xiě)QThread類(lèi)的run()函數(shù)的方法可以很好的解決耗時(shí)操作導(dǎo)致界面卡死的問(wèn)題。但也存在一些不足,比如增加額外的資源開(kāi)銷(xiāo)、程序整體執(zhí)行時(shí)間變長(zhǎng)了(變慢了);當(dāng)然在更好的操作體驗(yàn)面前這些都是可以忽略的,畢竟GUI(Graphical User Interface)的定義就是圖形用戶(hù)界面。
其他補(bǔ)充
應(yīng)該將信號(hào)綁定放在需要多次操作的函數(shù)外部,否則應(yīng)設(shè)置操作完成后的判斷條件去斷開(kāi)信號(hào)綁定;不然會(huì)出現(xiàn)信號(hào)多次綁定到同一槽函數(shù),結(jié)果就是一次信號(hào)傳遞執(zhí)行兩次已綁定槽函數(shù)。
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)總結(jié)
到此這篇關(guān)于PyQt5執(zhí)行耗時(shí)操作導(dǎo)致界面卡死或未響應(yīng)的原因及解決辦法的文章就介紹到這了,更多相關(guān)PyQt5執(zhí)行耗操作導(dǎo)致界面卡死內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Python使用DEAP庫(kù)實(shí)現(xiàn)簡(jiǎn)單遺傳算法
DEAP是一個(gè)用于快速原型設(shè)計(jì)和實(shí)驗(yàn)的進(jìn)化計(jì)算框架,它支持多種進(jìn)化算法,包括遺傳算法、遺傳編程等,下面我們來(lái)看看如何使用DEAP庫(kù)實(shí)現(xiàn)簡(jiǎn)單遺傳算法吧2025-01-01
python3使用requests模塊爬取頁(yè)面內(nèi)容的實(shí)戰(zhàn)演練
本篇文章主要介紹了python3使用requests模塊爬取頁(yè)面內(nèi)容的實(shí)戰(zhàn)演練,具有一定的參考價(jià)值,有興趣的可以了解一下2017-09-09
python單例模式之selenium driver實(shí)現(xiàn)單例
這篇文章主要介紹了python單例模式之selenium driver實(shí)現(xiàn)單例,使用裝飾器實(shí)現(xiàn)單例,文章基于python的相關(guān)資料實(shí)現(xiàn)主題,具有一的的參考價(jià)值,需要的朋友可以參考一下2022-03-03
Python歷史記錄管理之保存最后N個(gè)元素的完整指南
這篇文章主要為大家詳細(xì)介紹了Python歷史記錄管理之保存最后N個(gè)元素的相關(guān)知識(shí),文中的示例代碼講解詳細(xì),感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下2025-08-08
Python合并多個(gè)PDF文件的完整指南與實(shí)踐
在日常工作中,我們經(jīng)常需要將多個(gè)PDF文件合并為一個(gè),無(wú)論是整理報(bào)告、合并掃描文檔,還是準(zhǔn)備演示材料,本文將詳細(xì)介紹幾種使用Python合并PDF文件的方法,并提供完整的實(shí)踐代碼,需要的朋友可以參考下2026-01-01
Python調(diào)用adb命令實(shí)現(xiàn)對(duì)多臺(tái)設(shè)備同時(shí)進(jìn)行reboot的方法
今天小編就為大家分享一篇Python調(diào)用adb命令實(shí)現(xiàn)對(duì)多臺(tái)設(shè)備同時(shí)進(jìn)行reboot的方法,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2018-10-10

