在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?)。
当使用静态函数QTimer::singleShot时,实际上直接调用了QueuedConnection方式的 QMetaObject::invokeMethod()
每当需要一个计时器时,我们很容易想到QTimer,
如果看Qt的Manual,我们还会注意到QBasicTimer和QTimeLine这两个类,也可起到计时的作用。那么这些之间那个最为根本呢?
所有这些都归结到QObject的3个成员函数中:
下面我们看看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 *); ... };
大家应该想得到了(就不贴代码了):
这是一个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 事件。
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了。
我们以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; }
呵呵,这段挺复杂的:
对普通的timer,首先尝试启用多媒体定时器。若失败,则使用传统的SetTimer 和 KillTimer
我们先看看对正常的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,并没有启用系统的定时器,而是直接派发了一个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进行什么处理了吧?