起源
在newsmth上看到這樣一個問題:
發信人: hgoldfish (老魚), 信區: KDE_Qt標 題: QTimer::singleShot()的疑問發信站: 水木社區 (Mon Apr 11 22:03:48 2011), 站內singleShot(0, ...)是表示下面的哪種情況呢?1. 退出當前函數,回到事件迴圈的時候立即執行,忽略其它訊息。2. 把對應的QTimerEvent放到訊息佇列的最後,然後依次處理訊息。3. 把這個這個singleShot對應的QTimerEvent放到最後。依次處理訊息。但是如果有新的訊息到達時,它們會排到QTimerEvent的前面。其中,2是假定訊息佇列沒有優先順序。1、3假定訊息佇列有優先順序,但是1假定QTimerEvent最優先,而3假定QTimerEvent最不優先。看文檔似乎是3?
似乎挺有意思,於是,開啟Qt的源碼,慢慢看看,於是整理出本文。如果理解沒問題的話,應該可以得出這個結論:
一段廢話,作為本文引子
每當需要一個計時器時,我們很容易想到QTimer,
- 建立QTimer對象
- 串連它的訊號到我們的槽函數。
如果看Qt的Manual,我們還會注意到QBasicTimer和QTimeLine這兩個類,也可起到計時的作用。那麼這些之間那個最為根本呢?
所有這些都歸結到QObject的3個成員函數中:
- 在衍生類別中覆蓋timerEvent()函數,進行處理
- 通過startTimer()開啟計時器
- 通過killTimer() 結束
下面我們看看QTimer的計時事件是如何一步一步和系統提供的計時器(優先使用多媒體計時器,其次是普通的計時器)聯絡起來的
QTimer
看一下QTimer的源碼,一切都明了了:
class QTimer:public QObject{...public Q_SLOTS: void start(int msec); void start(); void stop();Q_SIGNALS: void timeout();protected: void timerEvent(QTimerEvent *);...};
大家應該想得到了(就不貼代碼了):
- start() 調用 QObject::startTimer()
- stop() 調用 QObject::killTimer()
- timerEvent() 中發射訊號 timeout()
QTimer::singleShot()
這是一個static成員函數,由於只需要一次事件。它其實沒有建立QTimer對象,而是使用了一個QSingleShotTimer對象。這個類完整定義很簡單
class QSingleShotTimer : public QObject{ Q_OBJECT int timerId;public: ~QSingleShotTimer(); QSingleShotTimer(int msec, QObject *r, const char * m);Q_SIGNALS: void timeout();protected: void timerEvent(QTimerEvent *);};
這個沒什麼什麼可介紹的,如果說特點的話,也就是 timerEvent 被調用一次後,就會將自己這個對象刪除(呵呵,有點廢話哈,調用一次使命就完成了唄)
另外呢,對於時間間隔為0的事件,甚至連QSingleShotTimer都不需要建立,而是直接用invokeMethod去調用相應的slot
:
void QTimer::singleShot(int msec, QObject *receiver, const char *member){ if (receiver && member) { if (msec == 0) { // special code shortpath for 0-timers const char* bracketPosition = strchr(member, '('); if (!bracketPosition || !(member[0] >= '0' && member[0] <= '3')) { qWarning("QTimer::singleShot: Invalid slot specification"); return; } QByteArray methodName(member+1, bracketPosition - 1 - member); // extract method name QMetaObject::invokeMethod(receiver, methodName.constData(), Qt::QueuedConnection); return; } (void) new QSingleShotTimer(msec, receiver, member); }}
在 QMetaObject::invokeMethod的分析中,我們知道:對於QueuedConnection的串連,它最終通過QCoreApplication的postEvent() 函數派發了一個 QMetaCallEvent 事件。
QObject
- 迴歸正題,看QObject的 startTimer和killTimer做了什麼:
int QObject::startTimer(int interval){ Q_D(QObject); d->pendTimer = true; // set timer flag return d->threadData->eventDispatcher->registerTimer(interval, this);}void QObject::killTimer(int id){ Q_D(QObject); if (d->threadData->eventDispatcher) d->threadData->eventDispatcher->unregisterTimer(id);}
代碼倒是挺短,只是負擔一下子交給eventDispatcher了。
QAbstractEventDispatcher
我們以windows下的情況為例看看吧:
void QEventDispatcherWin32::registerTimer(int timerId, int interval, QObject *object){ Q_D(QEventDispatcherWin32); register WinTimerInfo *t = new WinTimerInfo; t->dispatcher = this; t->timerId = timerId; t->interval = interval; t->obj = object; t->inTimerEvent = false; t->fastTimerId = 0; if (d->internalHwnd) d->registerTimer(t); d->timerVec.append(t); // store in timer vector d->timerDict.insert(t->timerId, t); // store timers in dict}bool QEventDispatcherWin32::unregisterTimer(int timerId){ Q_D(QEventDispatcherWin32); WinTimerInfo *t = d->timerDict.value(timerId); d->timerDict.remove(t->timerId); d->timerVec.removeAll(t); d->unregisterTimer(t); return true;}
進而,代碼進入了 QEventDispatcherWin32Private
void QEventDispatcherWin32Private::registerTimer(WinTimerInfo *t){ Q_Q(QEventDispatcherWin32); int ok = 0; if (t->interval > 20 || !t->interval || !qtimeSetEvent) { ok = 1; if (!t->interval) // optimization for single-shot-zero-timer QCoreApplication::postEvent(q, new QZeroTimerEvent(t->timerId)); else ok = SetTimer(internalHwnd, t->timerId, (uint) t->interval, 0); } else { ok = t->fastTimerId = qtimeSetEvent(t->interval, 1, qt_fast_timer_proc, (DWORD_PTR)t, TIME_CALLBACK_FUNCTION | TIME_PERIODIC | TIME_KILL_SYNCHRONOUS); if (ok == 0) { // fall back to normal timer if no more multimedia timers available ok = SetTimer(internalHwnd, t->timerId, (uint) t->interval, 0); } }}void QEventDispatcherWin32Private::unregisterTimer(WinTimerInfo *t, bool closingDown){ // mark timer as unused if (!QObjectPrivate::get(t->obj)->inThreadChangeEvent && !closingDown) QAbstractEventDispatcherPrivate::releaseTimerId(t->timerId); if (t->interval == 0) { QCoreApplicationPrivate::removePostedTimerEvent(t->dispatcher, t->timerId); } else if (t->fastTimerId != 0) { qtimeKillEvent(t->fastTimerId); QCoreApplicationPrivate::removePostedTimerEvent(t->dispatcher, t->timerId); } else if (internalHwnd) { KillTimer(internalHwnd, t->timerId); } delete t;}
呵呵,這段挺複雜的:
非0的timer
我們先看看對正常的timer,系統如何通知程式定時事件的呢?熟悉Windows編程的應該對這個回呼函數很熟悉吧(呵呵,我對windows編程不熟,說錯了別怪我哈)
LRESULT QT_WIN_CALLBACK qt_internal_proc(HWND hwnd, UINT message, WPARAM wp, LPARAM lp){if (message == WM_TIMER) { Q_ASSERT(d != 0); d->sendTimerEvent(wp); return 0;...}
然後,看看這個sendTimerEvent做了什麼
void QEventDispatcherWin32Private::sendTimerEvent(int timerId){ WinTimerInfo *t = timerDict.value(timerId); if (t && !t->inTimerEvent) { // send event, but don't allow it to recurse t->inTimerEvent = true; QTimerEvent e(t->timerId); QCoreApplication::sendEvent(t->obj, &e); // timer could have been removed t = timerDict.value(timerId); if (t) { t->inTimerEvent = false; } }}
挺簡單的,就是通過QCoreApplication的sendEvent函數派發了QTimerEvent事件,剩下的工作當然就是Qt的事件系統的任務了。
間隔為0的timer
我們前面說了,對於間隔為0的timer,並沒有啟用系統的定時器,而是直接派發了一個QZeroTimerEvent 事件。我們知道,它進入事件系統以後,將會被派發到event函數
bool QEventDispatcherWin32::event(QEvent *e){ Q_D(QEventDispatcherWin32); if (e->type() == QEvent::ZeroTimerEvent) { QZeroTimerEvent *zte = static_cast<QZeroTimerEvent*>(e); WinTimerInfo *t = d->timerDict.value(zte->timerId()); if (t) { t->inTimerEvent = true; QTimerEvent te(zte->timerId()); QCoreApplication::sendEvent(t->obj, &te); t = d->timerDict.value(zte->timerId()); if (t) { if (t->interval == 0 && t->inTimerEvent) { // post the next zero timer event as long as the timer was not restarted QCoreApplication::postEvent(this, new QZeroTimerEvent(zte->timerId())); } t->inTimerEvent = false; } } return true; } else if (e->type() == QEvent::Timer) { QTimerEvent *te = static_cast<QTimerEvent*>(e); d->sendTimerEvent(te->timerId()); } return QAbstractEventDispatcher::event(e);}
看到對QZeroTimerEvent進行什麼處理了吧?
- 通過 QCoreApplication 的 sendEvent 派發出 QTimerEvent 事件
- 同時 產生一個新的 QZeroTimerEvent 事件,放入事件隊列中