PyQt5高階介面控制元件之QThread(十二)
QThread
前言
QThread是Qt的執行緒類中最核心的底層類。由於PyQt的的跨平臺特性,QThread要隱藏所有與平臺相關的程式碼
要使用的QThread開始一個執行緒,可以建立它的一個子類,然後覆蓋其它QThread.run()函式
class Thread(QThread):
def __init __(self):
super(Thread,self).__ init __()
def run(self):
#執行緒相關的程式碼
pass
接下來建立一個新的執行緒
thread = Thread()
thread.start()
可以看出,PyQt的執行緒使用非常簡單—-建立一個自定義的類(如thread),自我繼承自QThread ,並實現其run()方法即可
在使用執行緒時可以直接得到Thread例項,呼叫其start()函式即可啟動執行緒,執行緒啟動之後,會自動呼叫其實現的run()的函式,該方法就是執行緒的執行函式
業務的執行緒任務就寫在run()函式中,當run()退出之後執行緒就基本結束了,QThread有started和finished訊號,可以為這兩個訊號指定槽函式,線上程啟動和結束之時執行一段程式碼進行資源的初始化和釋放操作,更靈活的使用方法是,在自定義的QThread例項中自定義訊號,並將訊號連線到指定的槽函式,當滿足一定的業務條件時發射此訊號
QThread類中的常用方法
方法 | 描述 |
---|---|
start() | 啟動執行緒 |
wait() | 阻止執行緒,直到滿足如下條件之一 |
與此QThread物件關聯的執行緒已完成執行(即從run返回時),如果執行緒完成執行,此函式返回True,如果執行緒尚未啟動,也返回True | |
等待時間的單位是毫秒,如果時間是ULONG_MAX(預設值·),則等待,永遠不會超時(執行緒必須從run返回),如果等待超時,此函式將會返回False | |
sleep() | 強制當前執行緒睡眠多少秒 |
QThread類中的常用訊號
訊號 | 描述 |
---|---|
started | 在開始執行run函式之前,從相關執行緒發射此訊號 |
finished | 當程式完成業務邏輯時,從相關執行緒發射此訊號 |
例項:QThread的使用
import sys
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
from PyQt5.QtGui import *
class MainWidget(QWidget):
def __init__(self, parent=None):
super(MainWidget, self).__init__(parent)
#設定標題
self.setWindowTitle('QThread多執行緒例子')
#例項化多執行緒物件
self.thread = Worker()
#例項化列表控制元件與按鈕控制元件
self.listFile = QListWidget()
self.btnStart = QPushButton('開始')
#把控制元件放置在柵格佈局中
layout = QGridLayout(self)
layout.addWidget(self.listFile, 0, 0, 1, 2)
layout.addWidget(self.btnStart, 1, 1)
#訊號與槽函式的連線
self.btnStart.clicked.connect(self.slotStart)
self.thread.sinOut.connect(self.slotAdd)
def slotAdd(self, file_inf):
#向列表控制元件中新增條目
self.listFile.addItem(file_inf)
def slotStart(self):
#開始按鈕不可點選,執行緒開始
self.btnStart.setEnabled(False)
self.thread.start()
class Worker(QThread):
sinOut = pyqtSignal(str)
def __init__(self, parent=None):
super(Worker, self).__init__(parent)
#設定工作狀態與初始num數值
self.working = True
self.num = 0
def __del__(self):
#執行緒狀態改變與執行緒終止
self.working = False
self.wait()
def run(self):
while self.working == True:
#獲取文字
file_str = 'File index{0}'.format(self.num)
self.num += 1
# 發射訊號
self.sinOut.emit(file_str)
# 執行緒休眠2秒
self.sleep(2)
if __name__ == '__main__':
app = QApplication(sys.argv)
demo = MainWidget()
demo.show()
sys.exit(app.exec_())
執行效果圖如下
程式碼分析
在這個例子中,單擊開始按鈕,會在後臺定時讀取資料,並把返回的資料顯示在介面中,首先使用以下程式碼進行佈局,把列表控制元件和按鈕控制元件放在柵格佈局管理器中
#例項化列表控制元件與按鈕控制元件
self.listFile = QListWidget()
self.btnStart = QPushButton('開始')
#把控制元件放置在柵格佈局中
layout = QGridLayout(self)
layout.addWidget(self.listFile, 0, 0, 1, 2)
layout.addWidget(self.btnStart, 1, 1)
然後將按鈕的clicked訊號連線到槽函式,單擊開始觸發槽函式
self.btnStart.clicked.connect(self.slotStart)
def slotStart(self):
#開始按鈕不可點選,執行緒開始
self.btnStart.setEnabled(False)
self.thread.start()
比較複雜的是執行緒的訊號,將執行緒的sinOut訊號連線到slotAdd()槽函式,SlotAdd()函式負責在列表控制元件中動態新增字串條目
self.thread.sinOut.connect(self.slotAdd)
def slotAdd(self,file_inf):
#向列表控制元件中新增條目
self.listFile.addItem(file_inf)
定義一個執行緒類,繼承自QThread,當執行緒啟動時,執行run()函式
class Worker(QThread):
sinOut = pyqtSignal(str)
def __init__(self, parent=None):
super(Worker, self).__init__(parent)
#設定工作狀態與初始num數值
self.working = True
self.num = 0
def __del__(self):
#執行緒狀態改變與執行緒終止
self.working = False
self.wait()
def run(self):
while self.working == True:
#獲取文字
file_str = 'File index{0}'.format(self.num)
self.num += 1
# 發射訊號
self.sinOut.emit(file_str)
# 執行緒休眠2秒
self.sleep(2)
例項二:多執行緒失敗案例
import sys
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
from PyQt5.QtGui import *
global sec
sec=0
def setTime():
global sec
sec+=1
#Led顯示數字+1
lcdNumber.display(sec)
def work():
#計時器每秒計數
timer.start(1000)
for i in range(200000000):
pass
timer.stop()
if __name__ == '__main__':
app=QApplication(sys.argv)
top=QWidget()
top.resize(300,120)
#垂直佈局
layout=QVBoxLayout(top)
#新增一個顯示面板
lcdNumber=QLCDNumber()
layout.addWidget(lcdNumber)
button=QPushButton('測試')
layout.addWidget(button)
timer=QTimer()
#每次計時結束,觸發setTime
timer.timeout.connect(setTime)
button.clicked.connect(work)
top.show()
sys.exit(app.exec_())
失敗效果圖如下
長時間停留在此介面,知道多執行緒任務完成後,此介面才會動,當耗時程式非常大時,就會造成程式執行失敗的假象,實際還是在後臺執行的,只是沒有顯示在主視窗的介面上,當然使用者體驗也就非常差,那麼如何解決這個問題呢,下面例項三進行解答
例項三:分離UI主執行緒與工作執行緒
import sys
from PyQt5.QtCore import *
from PyQt5.QtGui import *
from PyQt5.QtWidgets import *
global sec
sec = 0
class WorkThread(QThread):
#例項化一個訊號物件
trigger = pyqtSignal()
def __int__(self):
super(WorkThread, self).__init__()
def run(self):
#開始進行迴圈
for i in range(2000000000):
pass
# 迴圈完畢後發出訊號
self.trigger.emit()
def countTime():
global sec
sec += 1
# LED顯示數字+1
lcdNumber.display(sec)
def work():
# 計時器每秒計數
timer.start(1000)
# 計時開始
workThread.start()
# 當獲得迴圈完畢的訊號時,停止計數
workThread.trigger.connect(timeStop)
def timeStop():
#定時器停止
timer.stop()
print("執行結束用時", lcdNumber.value())
global sec
sec = 0
if __name__ == "__main__":
app = QApplication(sys.argv)
top = QWidget()
top.resize(300, 120)
# 垂直佈局類QVBoxLayout
layout = QVBoxLayout(top)
# 加顯示屏,按鈕到佈局中
lcdNumber = QLCDNumber()
layout.addWidget(lcdNumber)
button = QPushButton("測試")
layout.addWidget(button)
#例項化定時器與多執行緒類
timer = QTimer()
workThread = WorkThread()
button.clicked.connect(work)
# 每次計時結束,觸發 countTime
timer.timeout.connect(countTime)
top.show()
sys.exit(app.exec_())
執行效果,程式主介面的數值會每秒增加1,直到迴圈結束,這裡就避免了主介面長時間不動的尷尬!
例項四:事件處理
對於執行很耗時的程式來說,由於PyQt需要等待程式執行完畢才能進行下一步,這個過程表現在介面上就是卡頓,而如果需要執行這個耗時程式時不斷的重新整理介面。那麼就可以使用QApplication.processEvents(),那麼就可以一邊執行耗時程式,一邊重新整理介面的功能,給人的感覺就是程式執行很流暢,因此QApplicationEvents()的使用方法就是,在主函式執行耗時操作的地方,加入QApplication.processEvents()
import sys,time
from PyQt5.QtWidgets import QWidget,QPushButton,QApplication,QListWidget,QGridLayout
class WinForm(QWidget):
def __init__(self,parent=None):
super(WinForm, self).__init__(parent)
#設定標題與佈局方式
self.setWindowTitle('實時重新整理介面的例子')
layout=QGridLayout()
#例項化列表控制元件與按鈕控制元件
self.listFile=QListWidget()
self.btnStart=QPushButton('開始')
#新增到佈局中指定位置
layout.addWidget(self.listFile,0,0,1,2)
layout.addWidget(self.btnStart,1,1)
#按鈕的點選訊號觸發自定義的函式
self.btnStart.clicked.connect(self.slotAdd)
self.setLayout(layout)
def slotAdd(self):
for n in range(10):
#獲取條目文字
str_n='File index{0}'.format(n)
#新增文字到列表控制元件中
self.listFile.addItem(str_n)
#實時重新整理介面
QApplication.processEvents()
#睡眠一秒
time.sleep(1)
if __name__ == '__main__':
app=QApplication(sys.argv)
win=WinForm()
win.show()
sys.exit(app.exec_())