一、阻塞型延時
阻塞的原理就是:在延時期間,本線程的事件循環得不到執行。
1、QThread類的sleep()
最簡單的延時方法就是使用QThread類的sleep(n)、msleep(n)、usleep(n),這幾個函數的不良後果就是,GUI會在延時的時間段内失去響應,界面卡死,是以,這三個函數一般用在非GUI線程中。
QThread::msleep(50);//阻塞延時50ms
2、使用定時器:死等
void Delay_MSec_Suspend(unsigned int msec)
{
QTime _Timer = QTime::currentTime().addMSecs(msec);
while( QTime::currentTime() < _Timer );
}
二、非阻塞延時
原理無非就是利用事件循環,有兩種原理:
1、處理本線程的事件循環
在等待中,不斷強制進入目前線程的事件循環,這樣可以把堵塞的事件都處理掉,進而避免程式卡死
void Delay_MSec(unsigned int msec)
{
QTime _Timer = QTime::currentTime().addMSecs(msec);
while( QTime::currentTime() < _Timer )
QCoreApplication::processEvents(QEventLoop::AllEvents, 100);
2、使用子事件循環
建立子事件循環,在子事件循環中,父事件循環仍然是可以執行的
void Delay_MSec(unsigned int msec)
{
QEventLoop loop;//定義一個新的事件循環
QTimer::singleShot(msec, &loop, SLOT(quit()));//建立單次定時器,槽函數為事件循環的退出函數
loop.exec();//事件循環開始執行,程式會卡在這裡,直到定時時間到,本循環被退出
}
三、耗時代碼的處理
假設有這樣的應用情景:點選某個button之後,需要讀入并處理一幅圖像,需要耗時20秒才能處理完。
在這20s内,GUI會失去效應,界面上的任何元素都無法被點選,這種情況應該怎麼辦?方法有兩種:1、用另一個線程去處理這個耗時任務;2、在耗時任務中,不斷地去處理本線程的事件循環,以保證GUI的及時響應。
for(i=0; i < 1000000; i++)
{
//QCoreApplication::processEvents(QEventLoop::AllEvents); //去處理本線程的事件循環,避免本線程被堵塞
QCoreApplication::processEvents(QEventLoop::AllEvents, 5);//如果不夠頻繁,可以增加第二參數來緩解卡頓
for(j=0; j < 1000000; j++)
{
//QCoreApplication::processEvents(QEventLoop::AllEvents);//處理事件循環,不建議放在這裡,可能過于頻繁
doSomeThing();
}
}
一般來說,processEvents()不宜被調用的過于頻繁,也不宜被調用的不夠頻繁。過于頻繁的話,一方面會使線程的響應更好,但另一方面會導緻原本就耗時的任務變得更加耗時;不夠頻繁的話,顯然可能會使GUI線程的響應變差,例如每500ms才被調用一次,那麼GUI的事件循環就隻能500ms才被處理一次,當然,這個問題可以通過設定processEvents()的第二個形參略微得到緩解,更好的做法是,保證被調的周期<200ms(再小一些更好,看程式需求),這樣不至于肉眼可見的卡頓。
副作用:(特别注意!)
1、在點選按鈕之後,這個20s的耗時任務開始執行,尚未執行完畢時,我們點選了GUI的關閉按鈕,那麼GUI會立即消失,但是這個耗時任務仍然會在背景執行,直到執行完畢,程序才會退出。解決辦法:重寫關閉事件,在關閉事件的函數中直接結束程序。
2、在點選按鈕之後,這個20s的耗時任務開始執行,執行到第5秒時,我們再次點選了這個按鈕,那麼QT又會執行一個新的20s任務,這個新任務完成後,又會接着把第一個20s任務從上次被打斷的第5秒繼續執行。如果這個任務是可重入的,後果僅僅是被執行了兩遍,如果任務不可重入,那情況就徹底糟糕了。解決辦法:點選按鈕後把這個按鈕disable掉,執行完再enable
轉載自:嵌入式應用研究院
文章來源于QT開發中延時的幾種處理方法
原文連結:https://mp.weixin.qq.com/s/s1GMw3fsdhXetc9E-e7c4w