單位的公共儀器需要預(yù)約才能夠使用包券,但是自從預(yù)約時間改到了早上介袜,就很不好約了痹兜,所以決定寫一個程序?qū)崿F(xiàn)自動預(yù)約蛮粮。由于還要分享給實驗室其它同學(xué)使用益缎,就得寫一個GUI了,這一下代碼量瞬間變成了原來的10倍蝉揍,不過學(xué)到了些新東西链峭,這波不虧。
在圖形化編程中一個很重要的一點就是使用多線程又沾,將UI線程獨立出來弊仪。如果你的程序不存在會造成線程阻塞的操作熙卡,不使用多線程倒也沒什么大問題,但是如果存在像聯(lián)網(wǎng)励饵、讀寫文件等可能需要等待的操作驳癌,使用單線程就可能會造成UI假死,這對于用戶體驗來說是非常不友好的役听。例如微軟的office套件為了兼容老式CPU颓鲜,IO等操作都是使用的UI的線程,打開或保存大文件時典予,用戶界面無響應(yīng)甜滨;而像photoshop這樣的程序由于使用了多線程,即使有大量的IO行為瘤袖,UI也可以響應(yīng)用戶的操作衣摩。
Qt引以為豪的對象通訊機制被稱為信號-槽(signal-slot)機制。當(dāng)特定事件被觸發(fā)時(如子線程結(jié)束)將發(fā)送一個信號捂敌,而與該信號建立的連接槽艾扮,則可以接收到該信號并做出反應(yīng)(根據(jù)子線程的返回值執(zhí)行操作)。
在我的程序中占婉,需要在儀器開放預(yù)約前不斷查詢泡嘴,在開放預(yù)約的第一刻立即提交表單,而為了不拖垮服務(wù)器逆济,需要在每次查詢后讓線程休眠一會酌予。如果所有這些操作都在主線程中完成,那么線程休眠的時候UI就會無響應(yīng)纹腌,在我的程序中就表現(xiàn)為點擊開始查詢后UI就一直處于無響應(yīng)狀態(tài)霎终,直到預(yù)約成功后才恢復(fù)響應(yīng)。
因此升薯,就需要新建一個線程莱褒,這個線程只用來發(fā)送信號調(diào)用查詢函數(shù),發(fā)送完畢后即休眠涎劈,等待下一次發(fā)送或任務(wù)完成后結(jié)束線程广凸。
要創(chuàng)建一個新線程,需要在程序中定義一個類蛛枚,這個類要繼承QtCore.QThread
谅海,然后把要執(zhí)行的操作放進run()
函數(shù)中,線程啟動后蹦浦,就會自動運行這個函數(shù)扭吁。結(jié)束線程時通過調(diào)用線程的terminate()
方法來中止線程:
class QueryWaitThread(QtCore.QThread):
# 定義線程需要用到的信號
query_signal = QtCore.pyqtSignal() # 查詢信號
on_complete = QtCore.pyqtSignal(int) # 每次查詢完成時發(fā)送,可以發(fā)送參數(shù),
此處參數(shù)int為查詢次數(shù)
terminate_thread = QtCore.pyqtSignal() # 結(jié)束線程信號
def __init__(self):
super(QueryWaitThread, self).__init__()
def run(self): # 只發(fā)送信號侥袜,然后休眠蝌诡。
i = 1
while True:
self.on_complete.emit(i) # 調(diào)用信號的emit()方法
i += 1
self.query_signal.emit()
sleep(60)
def kill_thread(self):
self.terminate()
我的查詢函數(shù)與驗證函數(shù):
def auto_query(self):
################
# 查詢操作 #
################
if self.good_to_submit():
self.btnstop.hide()
self.btn_resel.show()
main.Appoint(self)
def good_to_submit(self):
if # 還無法提交表單:
return False
self.T.terminate_thread.emit() # 驗證可以提交表單后發(fā)送中止信號
return True
def print_current_count(self, i):
self.normalOutputWritten("當(dāng)前為第 %i 次查詢...\n" % i)
self.normalOutputWritten()
是一個我自己定義的函數(shù),用于在窗體的文本框中打印信息枫吧,你也可以定義自己的這類函數(shù)浦旱。
定義好信號以后,接下來需要調(diào)用信號的connect()
方法將信號連接到需要執(zhí)行函數(shù)上九杂。下面的start()
和stop()
是窗體按鈕按下后執(zhí)行的操作颁湖,我們在這兩個函數(shù)中使用剛剛定義好的信號:
def start(self):
################
# do something #
################
self.T = QueryWaitThread()
self.T.query_signal.connect(self.auto_query)
self.T.on_complete.connect(self.print_current_count)
self.T.terminate_thread.connect(self.T.kill_thread)
self.normalOutputWritten("已開啟自動查詢,當(dāng)有符合條件的時間段時將自動提交預(yù)約...\n")
self.T.start() # 開始執(zhí)行線程函數(shù)
self.btnstop.show()
self.btn_resel.hide()
def stop(self):
self.btnstop.hide()
self.btn_resel.show()
self.T.terminate_thread.emit() # 發(fā)送中止信號
self.normalOutputWritten("用戶終止查詢例隆。\n")
當(dāng)按下開始按鈕后甥捺,程序會構(gòu)造出一個線程T
,執(zhí)行到T.start()
后镀层,其中的run()
函數(shù)便會進入while
循環(huán)涎永,不斷發(fā)送查詢信號,直到good_to_submit()
函數(shù)返回True
表明已經(jīng)可以提交表單鹿响,或者當(dāng)停止按鈕按下后終止。
改進后的程序只是將定時功能分離了出來谷饿,實際上聯(lián)網(wǎng)查詢功能還是使用主線程完成的惶我,在程序等待服務(wù)器返回數(shù)據(jù)的過程中UI還是會有短暫的無響應(yīng)時間,不過這是在用戶點擊后產(chǎn)生的博投,因此對體驗影響不大绸贡。但是,嚴格來說聯(lián)網(wǎng)也應(yīng)該分離出一個線程毅哗,畢竟服務(wù)器有時候返回會很慢听怕,這時候如果你的用戶是一個暴躁老哥,還是會對體驗有影響的虑绵。