QTimer源碼分析(以Windows下實現為例)

來源:互聯網
上載者:User


起源

在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的源碼,慢慢看看,於是整理出本文。如果理解沒問題的話,應該可以得出這個結論:

  • 訊息佇列是有優先順序的。但是對與timerEvent事件,沒用到優先順序(Qt::NormalEventPriority?)。

  • 對於間隔不為零的timer,調用系統提供的計時器,然後等待響應系統的計時器事件
  • 對於間隔為零的timer,Qt自己進行了特殊處理。
    • 當使用靜態函數QTimer::singleShot時,實際上直接調用了QueuedConnection方式的 QMetaObject::invokeMethod()

    • 當啟動間隔為零的QTimer時,實際上先postEvent()派發了一個 QZeroTimerEvent到QAbstractEventDiapatch自身。在該事件的處理中,再通過sendEvent() 派發 QTimerEvent事件到對象中
一段廢話,作為本文引子

每當需要一個計時器時,我們很容易想到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,實際上並沒有啟動系統的計時器,而是建立了一個QZeroTimerEvent 事件
  • 對普通的timer,首先嘗試啟用多媒體定時器。若失敗,則使用傳統的SetTimer 和 KillTimer

非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 事件,放入事件隊列中

 

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.