[ PyQt入門教程 ] PyQt5中多執行緒模組QThread使用方法
本文主要講解使用多執行緒模組QThread解決PyQt介面程式唉執行耗時操作時,程式卡頓出現的無響應以及介面輸出無法實時顯示的問題。使用者使用工具過程中出現這些問題時會誤以為程式出錯,從而把程式關閉。這樣,導致工具的使用者使用體驗不好。下面我們通過模擬上述出現的問題並講述使用多執行緒QThread模組解決此類問題的方法。
PyQt程式卡頓和無法實時顯示問題現象
使用PyQt介面程式,點選執行按鈕後,程式在顯示框中每秒列印1個數字。程式程式碼如下:
# -*- coding: utf-8 -*- import sys import time from PyQt5.QtCore import QThread, pyqtSignal from PyQt5.QtWidgets import QApplication, QMainWindow from QThread_Example_UI import Ui_Form class MyMainForm(QMainWindow, Ui_Form): def __init__(self, parent=None): super(MyMainForm, self).__init__(parent) self.setupUi(self) self.runButton.clicked.connect(self.display) def display(self): for i in range(20): time.sleep(1) self.listWidget.addItem(str(i)) if __name__ == "__main__": app = QApplication(sys.argv) myWin = MyMainForm() myWin.show() sys.exit(app.exec_())
程式執行過程結果如下(點選Run按鈕後介面出現未響應字樣,同時程式也沒有出現每隔1秒列印1個數字,實際結果是迴圈結束後20個數字一同展示):
問題分析
上述實現的GUI程式都是單執行緒執行,對於需要執行一個特別耗時的操作時就會出現該問題現象。要解決這種問題可以考慮使用多執行緒模組QThread。
多執行緒模組QThread基本原理
QThread是Qt的執行緒類中最核心的底層類。由於PyQt的的跨平臺特性,QThread要隱藏所有與平臺相關的程式碼 要使用的QThread開始一個執行緒,可以建立它的一個子類,然後覆蓋其它QThread.run()函式。
class Thread(QThread): def __init__(self): super(Thread,self).__init__() def run(self): #
接下來建立一個新的執行緒
thread = Thread() thread.start()
可以看出,PyQt的執行緒使用非常簡單,建立一個自定義的類(如Thread),自我繼承自QThread ,並實現其run()方法即可。在使用執行緒時可以直接得到Thread例項,呼叫其start()函式即可啟動執行緒,執行緒啟動之後,會自動呼叫其實現的run()的函式,該方法就是執行緒的執行函式 。
業務的執行緒任務就寫在run()函式中,當run()退出之後執行緒就基本結束了,QThread有started和finished訊號,可以為這兩個訊號指定槽函式,線上程啟動和結束之時執行一段程式碼進行資源的初始化和釋放操作,更靈活的使用方法是,在自定義的QThread例項中自定義訊號,並將訊號連線到指定的槽函式,當滿足一定的業務條件時發射此訊號。
QThread類中的常用方法
start():啟動執行緒
wait():阻止執行緒,直到滿足如下條件之一
(1)與此QThread物件關聯的執行緒已完成執行(即從run返回時),如果執行緒完成執行,此函式返回True,如果執行緒尚未啟動,也返回True
(2)等待時間的單位是毫秒,如果時間是ULONG_MAX(預設值·),則等待,永遠不會超時(執行緒必須從run返回),如果等待超時,此函式將會返回False
sleep():強制當前執行緒睡眠多少秒
QThread類中的常用訊號
started:在開始執行run函式之前,從相關執行緒發射此訊號
finished:當程式完成業務邏輯時,從相關執行緒發射此訊號
使用QThread重新實現程式解決問題
先繼承QThread類並重新實現其中的run()函式,也就是說把耗時的操作放入run()函式中。程式碼如下:
# -*- coding: utf-8 -*- import sys import time from PyQt5.QtCore import QThread, pyqtSignal from PyQt5.QtWidgets import QApplication, QMainWindow from QThread_Example_UI import Ui_Form class MyMainForm(QMainWindow, Ui_Form): def __init__(self, parent=None): super(MyMainForm, self).__init__(parent) self.setupUi(self) # 例項化執行緒物件 self.work = WorkThread() self.runButton.clicked.connect(self.execute) def execute(self): # 啟動執行緒 self.work.start() # 執行緒自定義訊號連線的槽函式 self.work.trigger.connect(self.display) def display(self,str): # 由於自定義訊號時自動傳遞一個字串引數,所以在這個槽函式中要接受一個引數 self.listWidget.addItem(str) class WorkThread(QThread): # 自定義訊號物件。引數str就代表這個訊號可以傳一個字串 trigger = pyqtSignal(str) def __int__(self): # 初始化函式 super(WorkThread, self).__init__() def run(self): #重寫執行緒執行的run函式 #觸發自定義訊號 for i in range(20): time.sleep(1) # 通過自定義訊號把待顯示的字串傳遞給槽函式 self.trigger.emit(str(i)) if __name__ == "__main__": app = QApplication(sys.argv) myWin = MyMainForm() myWin.show() sys.exit(app.exec_())
程式執行結果如下(實現了每隔1秒列印1個數字):
小結
如果你實現的工具需要執行特別耗時的操作,可以參考使用本文多執行緒QThread處理方法實現。當然,工具實際實現過程中的場景會比這複雜。比如,你的輸出並不是有固定時間間隔輸出的文字框,可以嘗試使用多次self.trigger.emit(str)方法進行操