在不開啟事件循環的線程中使用QTimer(QThread::run函數自帶事件循環,在構造函數里創建線程,是一種很有意思的線程用法) good


引入

QTimer是Qt自帶的定時器類,QTimer運行時是依賴於事件循環的,簡單來說,在一個不開啟事件循環(未調用exec() )的線程中,QTimer是無法使用的。通過分析Qt源碼可發現,調用QTimer::start()后僅僅是在系統的定時器向量表中添加了一個定時器對象,但定時器並沒有真正開啟。定時器的開啟需要通過processEvent()開始的一系列調用后才會真正得開啟,這個過程中會處理定時器向量表中所有的定時器對象。那么實際exec()中也是在不斷地調用processEvent()方法。

 

問題

在項目中可能會遇到某條常駐線程,run()中運行着一個處理事務的死循環,在死循環中若想直接使用QTimer來實現定時功能,那么是不行的。我自己的的項目中是為了實現串口讀寫的超時判斷,所以需要用到定時器。

 

解決

網上可以搜到解決方案,並且很好用,但是存在問題。先說一下思路:

定時器對象需要在一個開啟事件循環的線程中運行,那么通過moveToThread() 方法我們將它移至一個開啟了事件循環的線程中運行就可以了。很happy,我們很快的封裝了一個定時器類,包含了一個QTimer及QThread對象,直接使用QThread對象是因為我的Qt版本是4.8.3,run()不是純虛函數,默認開啟了事件循環。主要代碼如下:

[cpp]  view plain  copy
 
 print?在CODE上查看代碼片派生到我的代碼片
  1. #ifndef QCUSTOMTIMER_H  
  2. #define QCUSTOMTIMER_H  
  3.   
  4. #include <QObject>  
  5. #include <QTimer>  
  6. #include <QThread>  
  7.   
  8. class QCustomTimer : public QObject  
  9. {  
  10.     Q_OBJECT  
  11. public:  
  12.     explicit QCustomTimer(QObject *parent = 0);  
  13.     ~QCustomTimer();  
  14.   
  15. private:  
  16.     QTimer      *m_pTimer;       //定時器對象  
  17.     QThread     *m_pTimerThread; //定時器依賴線程  
  18.   
  19. signals:  
  20.     void startSignal( int nMsc );//開啟定時器信號  
  21.     void stopSignal();           //停止定時器信號  
  22.     void TimeOut();              //定時器觸發,外部需連接此信號  
  23.     void deletelater();          //延時刪除定時器信號  
  24. public slots:  
  25.     void onTimer();              //對象內部定時觸發槽函數,向外部發射定時器觸發信號  
  26. public:  
  27.     void StartTimer( int nMsc ); //開啟定時器  
  28.     void StopTimer();            //關閉定時器  
  29.     void DeleteLater();          //延時刪除定時器對象  
  30.   
  31. };  
  32.   
  33. #endif // QCUSTOMTIMER_H  
[cpp]  view plain  copy
 
 print?在CODE上查看代碼片派生到我的代碼片
  1.   
[cpp]  view plain  copy
 
 print?在CODE上查看代碼片派生到我的代碼片
  1. #include "qcustomtimer.h"  
  2.   
  3. QCustomTimer::QCustomTimer(QObject *parent) :  
  4.     QObject(parent)  
  5. {  
  6.     m_pTimer = new QTimer(0);  
  7.     m_pTimer->setSingleShot( true );//單次觸發  
  8.   
  9.     m_pTimerThread->start();  
  10.   
  11.     m_pTimer->moveToThread( m_pTimerThread );//更改定時器運行的線程  
  12.   
  13.     connect( m_pTimer, SIGNAL(timeout()), this, SLOT(onTimer()) , Qt::DirectConnection );//定時器事件觸發槽  
  14.   
  15.     connect( this, SIGNAL(startSignal(int)), m_pTimer, SLOT(start( int ) ), Qt::BlockingQueuedConnection );//連接定時器啟動槽函數,不可用“直連”  
  16.   
  17.     connect( this, SIGNAL(stopSignal()), m_pTimer, SLOT(stop()), Qt::BlockingQueuedConnection );//連接定時器關閉槽函數,不可用“直連”  
  18.   
  19.     connect( this, SIGNAL( deletelater() ), m_pTimer, SLOT(deleteLater()) );//刪除位於線程中的定時器對象,插入一個延時刪除的事件  
  20. }  
  21.   
  22. QCustomTimer::~QCustomTimer()  
  23. {  
  24.     StopTimer();  
  25.     DeleteLater();  
  26. }  
  27.   
  28. void QCustomTimer::onTimer()  
  29. {  
  30.     emit TimeOut();//發射定時器觸發信號  
  31. }  
  32.   
  33. void QCustomTimer::StartTimer(int nMsc)  
  34. {  
  35.     emit startSignal(nMsc) ;//向子線程內的定時器發送開啟定時器信號  
  36. }  
  37.   
  38. void QCustomTimer::StopTimer()  
  39. {  
  40.     emit stopSignal();//向子線程內的定時器發送停止定時器信號  
  41. }  
  42.   
  43. void QCustomTimer::DeleteLater()  
  44. {  
  45.     emit deletelater();//向子線程的事件循環插入一個延期刪除事件  
  46. }  


實際在初步使用時,毫無問題,定時器正常觸發。但是當非常頻繁得創建及析構QCustomTimer對象時,則會出現崩潰等問題。比較慚愧,此問題困擾我很久,雖然最后解決了問題,但我仍然不知道是什么原因導致,若有大牛知道,請您一定要告訴我!!

 

下面說說上述的代碼:

1. 調用moveToThread()方法后,定時器對象就屬於子線程的了,那么要釋放的話,按照文檔來說要么是插入deleteLater()延時刪除事件,要么是等run()函數返回前會將對象釋放掉。上述代碼中使用的是指針,不使用指針而使用成員變量的形式,我也嘗試過,程序仍然會崩潰。

2. 多說一點,就是程序崩潰基本要幾個小時才會崩,程序報錯 “純虛函數被調用”,針對這個線索google了很多資料,但是沒有找到應對方案。

3. 猜測還是在高頻使用的情況下,定時器對象及線程對象的析構出現了問題, 且deleteLater()無法解決。

 

我的解決方法,其實也不嚴謹,因為我並沒有精確定位到BUG。我是將線程對象改為靜態的,即去除了線程對象的析構,只需要正確析構掉定時器對象,這是deleteLater()就有了效果。代碼如下:

 

[cpp]  view plain  copy
 
 print?在CODE上查看代碼片派生到我的代碼片
  1. #ifndef QCUSTOMTIMER_H  
  2. #define QCUSTOMTIMER_H  
  3.   
  4. #include <QObject>  
  5. #include <QTimer>  
  6. #include <QThread>  
  7.   
  8. class QCustomTimer : public QObject  
  9. {  
  10.     Q_OBJECT  
  11. public:  
  12.     explicit QCustomTimer(QObject *parent = 0);  
  13.     ~QCustomTimer();  
  14.   
  15. private:  
  16.     static QThread     *m_pTimerThread; //定時器依賴線程  
  17.     QTimer             *m_pTimer;       //定時器對象  
  18.   
  19. signals:  
  20.     void startSignal( int nMsc );//開啟定時器信號  
  21.     void stopSignal();           //停止定時器信號  
  22.     void TimeOut();              //定時器觸發,外部需連接此信號  
  23.     void deletelater();          //延時刪除定時器信號  
  24. public slots:  
  25.     void onTimer();              //對象內部定時觸發槽函數,向外部發射定時器觸發信號  
  26. public:  
  27.     void StartTimer( int nMsc ); //開啟定時器  
  28.     void StopTimer();            //關閉定時器  
  29.     void DeleteLater();          //延時刪除定時器對象  
  30.   
  31. };  
  32.   
  33. #endif // QCUSTOMTIMER_H  

 

 

 

[cpp]  view plain  copy
 
 print?在CODE上查看代碼片派生到我的代碼片
  1. #include "qcustomtimer.h"  
  2.   
  3. //靜態線程成員指針初始化  
  4. QThread* QCustomTimer::m_pTimerThread = NULL;  
  5.   
  6. QCustomTimer::QCustomTimer(QObject *parent) :  
  7.     QObject(parent)  
  8. {  
  9.     if ( m_pTimerThread == NULL )  
  10.     {  
  11.         //此判斷分支依賴於Qt將靜態成員指針變量初始化為NULL,不是好的做法  
  12.         //但如果工程中存在其他全局變量並且有了依賴關系,那么應該也就只能這樣了  
  13.         //保證QCustomTimer類不要跟其他全局變量產生依賴關系,可以在類外初始化,這樣更好  
  14.         m_pTimerThread = new QThread;  
  15.     }  
  16.   
  17.     m_pTimer = new QTimer(0);  
  18.     m_pTimer->setSingleShot( true );//單次觸發  
  19.   
  20.     m_pTimerThread->start();  
  21.   
  22.     m_pTimer->moveToThread( m_pTimerThread );//更改定時器運行的線程  
  23.   
  24.     connect( m_pTimer, SIGNAL(timeout()), this, SLOT(onTimer()) , Qt::DirectConnection );//定時器事件觸發槽  
  25.   
  26.     connect( this, SIGNAL(startSignal(int)), m_pTimer, SLOT(start( int ) ), Qt::BlockingQueuedConnection );//連接定時器啟動槽函數,不可用“直連”  
  27.   
  28.     connect( this, SIGNAL(stopSignal()), m_pTimer, SLOT(stop()), Qt::BlockingQueuedConnection );//連接定時器關閉槽函數,不可用“直連”  
  29.   
  30.     connect( this, SIGNAL( deletelater() ), m_pTimer, SLOT(deleteLater()) );//刪除位於線程中的定時器對象,插入一個延時刪除的事件  
  31. }  
  32.   
  33. QCustomTimer::~QCustomTimer()  
  34. {  
  35.     StopTimer();  
  36.     DeleteLater();  
  37. }  
  38.   
  39. void QCustomTimer::onTimer()  
  40. {  
  41.     emit TimeOut();//發射定時器觸發信號  
  42. }  
  43.   
  44. void QCustomTimer::StartTimer(int nMsc)  
  45. {  
  46.     emit startSignal(nMsc) ;//向子線程內的定時器發送開啟定時器信號  
  47. }  
  48.   
  49. void QCustomTimer::StopTimer()  
  50. {  
  51.     emit stopSignal();//向子線程內的定時器發送停止定時器信號  
  52. }  
  53.   
  54. void QCustomTimer::DeleteLater()  
  55. {  
  56.     emit deletelater();//向子線程的事件循環插入一個延期刪除事件  
  57. }  


需要說明的就是靜態線程的初始化位置,我的做法是個權宜之計,因工程中其他全局變量與定時器類產生了依賴關系,導致只能這樣做。關於設計模式還是需要好好學習,避免這些糟糕的設計。

 

上述就是完整的代碼,如果有bug或者問題,歡迎給我留言!!

 

http://blog.csdn.net/u013709994/article/details/22175919


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM