引入
QTimer是Qt自帶的定時器類,QTimer運行時是依賴於事件循環的,簡單來說,在一個不開啟事件循環(未調用exec() )的線程中,QTimer是無法使用的。通過分析Qt源碼可發現,調用QTimer::start()后僅僅是在系統的定時器向量表中添加了一個定時器對象,但定時器並沒有真正開啟。定時器的開啟需要通過processEvent()開始的一系列調用后才會真正得開啟,這個過程中會處理定時器向量表中所有的定時器對象。那么實際exec()中也是在不斷地調用processEvent()方法。
問題
在項目中可能會遇到某條常駐線程,run()中運行着一個處理事務的死循環,在死循環中若想直接使用QTimer來實現定時功能,那么是不行的。我自己的的項目中是為了實現串口讀寫的超時判斷,所以需要用到定時器。
解決
網上可以搜到解決方案,並且很好用,但是存在問題。先說一下思路:
定時器對象需要在一個開啟事件循環的線程中運行,那么通過moveToThread() 方法我們將它移至一個開啟了事件循環的線程中運行就可以了。很happy,我們很快的封裝了一個定時器類,包含了一個QTimer及QThread對象,直接使用QThread對象是因為我的Qt版本是4.8.3,run()不是純虛函數,默認開啟了事件循環。主要代碼如下:
- #ifndef QCUSTOMTIMER_H
- #define QCUSTOMTIMER_H
- #include <QObject>
- #include <QTimer>
- #include <QThread>
- class QCustomTimer : public QObject
- {
- Q_OBJECT
- public:
- explicit QCustomTimer(QObject *parent = 0);
- ~QCustomTimer();
- private:
- QTimer *m_pTimer; //定時器對象
- QThread *m_pTimerThread; //定時器依賴線程
- signals:
- void startSignal( int nMsc );//開啟定時器信號
- void stopSignal(); //停止定時器信號
- void TimeOut(); //定時器觸發,外部需連接此信號
- void deletelater(); //延時刪除定時器信號
- public slots:
- void onTimer(); //對象內部定時觸發槽函數,向外部發射定時器觸發信號
- public:
- void StartTimer( int nMsc ); //開啟定時器
- void StopTimer(); //關閉定時器
- void DeleteLater(); //延時刪除定時器對象
- };
- #endif // QCUSTOMTIMER_H
- #include "qcustomtimer.h"
- QCustomTimer::QCustomTimer(QObject *parent) :
- QObject(parent)
- {
- m_pTimer = new QTimer(0);
- m_pTimer->setSingleShot( true );//單次觸發
- m_pTimerThread->start();
- m_pTimer->moveToThread( m_pTimerThread );//更改定時器運行的線程
- connect( m_pTimer, SIGNAL(timeout()), this, SLOT(onTimer()) , Qt::DirectConnection );//定時器事件觸發槽
- connect( this, SIGNAL(startSignal(int)), m_pTimer, SLOT(start( int ) ), Qt::BlockingQueuedConnection );//連接定時器啟動槽函數,不可用“直連”
- connect( this, SIGNAL(stopSignal()), m_pTimer, SLOT(stop()), Qt::BlockingQueuedConnection );//連接定時器關閉槽函數,不可用“直連”
- connect( this, SIGNAL( deletelater() ), m_pTimer, SLOT(deleteLater()) );//刪除位於線程中的定時器對象,插入一個延時刪除的事件
- }
- QCustomTimer::~QCustomTimer()
- {
- StopTimer();
- DeleteLater();
- }
- void QCustomTimer::onTimer()
- {
- emit TimeOut();//發射定時器觸發信號
- }
- void QCustomTimer::StartTimer(int nMsc)
- {
- emit startSignal(nMsc) ;//向子線程內的定時器發送開啟定時器信號
- }
- void QCustomTimer::StopTimer()
- {
- emit stopSignal();//向子線程內的定時器發送停止定時器信號
- }
- void QCustomTimer::DeleteLater()
- {
- emit deletelater();//向子線程的事件循環插入一個延期刪除事件
- }
實際在初步使用時,毫無問題,定時器正常觸發。但是當非常頻繁得創建及析構QCustomTimer對象時,則會出現崩潰等問題。比較慚愧,此問題困擾我很久,雖然最后解決了問題,但我仍然不知道是什么原因導致,若有大牛知道,請您一定要告訴我!!
下面說說上述的代碼:
1. 調用moveToThread()方法后,定時器對象就屬於子線程的了,那么要釋放的話,按照文檔來說要么是插入deleteLater()延時刪除事件,要么是等run()函數返回前會將對象釋放掉。上述代碼中使用的是指針,不使用指針而使用成員變量的形式,我也嘗試過,程序仍然會崩潰。
2. 多說一點,就是程序崩潰基本要幾個小時才會崩,程序報錯 “純虛函數被調用”,針對這個線索google了很多資料,但是沒有找到應對方案。
3. 猜測還是在高頻使用的情況下,定時器對象及線程對象的析構出現了問題, 且deleteLater()無法解決。
我的解決方法,其實也不嚴謹,因為我並沒有精確定位到BUG。我是將線程對象改為靜態的,即去除了線程對象的析構,只需要正確析構掉定時器對象,這是deleteLater()就有了效果。代碼如下:
- #ifndef QCUSTOMTIMER_H
- #define QCUSTOMTIMER_H
- #include <QObject>
- #include <QTimer>
- #include <QThread>
- class QCustomTimer : public QObject
- {
- Q_OBJECT
- public:
- explicit QCustomTimer(QObject *parent = 0);
- ~QCustomTimer();
- private:
- static QThread *m_pTimerThread; //定時器依賴線程
- QTimer *m_pTimer; //定時器對象
- signals:
- void startSignal( int nMsc );//開啟定時器信號
- void stopSignal(); //停止定時器信號
- void TimeOut(); //定時器觸發,外部需連接此信號
- void deletelater(); //延時刪除定時器信號
- public slots:
- void onTimer(); //對象內部定時觸發槽函數,向外部發射定時器觸發信號
- public:
- void StartTimer( int nMsc ); //開啟定時器
- void StopTimer(); //關閉定時器
- void DeleteLater(); //延時刪除定時器對象
- };
- #endif // QCUSTOMTIMER_H
- #include "qcustomtimer.h"
- //靜態線程成員指針初始化
- QThread* QCustomTimer::m_pTimerThread = NULL;
- QCustomTimer::QCustomTimer(QObject *parent) :
- QObject(parent)
- {
- if ( m_pTimerThread == NULL )
- {
- //此判斷分支依賴於Qt將靜態成員指針變量初始化為NULL,不是好的做法
- //但如果工程中存在其他全局變量並且有了依賴關系,那么應該也就只能這樣了
- //保證QCustomTimer類不要跟其他全局變量產生依賴關系,可以在類外初始化,這樣更好
- m_pTimerThread = new QThread;
- }
- m_pTimer = new QTimer(0);
- m_pTimer->setSingleShot( true );//單次觸發
- m_pTimerThread->start();
- m_pTimer->moveToThread( m_pTimerThread );//更改定時器運行的線程
- connect( m_pTimer, SIGNAL(timeout()), this, SLOT(onTimer()) , Qt::DirectConnection );//定時器事件觸發槽
- connect( this, SIGNAL(startSignal(int)), m_pTimer, SLOT(start( int ) ), Qt::BlockingQueuedConnection );//連接定時器啟動槽函數,不可用“直連”
- connect( this, SIGNAL(stopSignal()), m_pTimer, SLOT(stop()), Qt::BlockingQueuedConnection );//連接定時器關閉槽函數,不可用“直連”
- connect( this, SIGNAL( deletelater() ), m_pTimer, SLOT(deleteLater()) );//刪除位於線程中的定時器對象,插入一個延時刪除的事件
- }
- QCustomTimer::~QCustomTimer()
- {
- StopTimer();
- DeleteLater();
- }
- void QCustomTimer::onTimer()
- {
- emit TimeOut();//發射定時器觸發信號
- }
- void QCustomTimer::StartTimer(int nMsc)
- {
- emit startSignal(nMsc) ;//向子線程內的定時器發送開啟定時器信號
- }
- void QCustomTimer::StopTimer()
- {
- emit stopSignal();//向子線程內的定時器發送停止定時器信號
- }
- void QCustomTimer::DeleteLater()
- {
- emit deletelater();//向子線程的事件循環插入一個延期刪除事件
- }
需要說明的就是靜態線程的初始化位置,我的做法是個權宜之計,因工程中其他全局變量與定時器類產生了依賴關系,導致只能這樣做。關於設計模式還是需要好好學習,避免這些糟糕的設計。
上述就是完整的代碼,如果有bug或者問題,歡迎給我留言!!
http://blog.csdn.net/u013709994/article/details/22175919