引入 在查看关于processEvent
时对于其第二个参数产生疑惑,Qt
是如何保持高精度的计时的呢?
而在需要一个计时器时,通常会想到QTimer
,通常对其的使用方式有两种:
重复可用的计时器:创建一个QTimer
对象,每过一段时间进行一个操作,即将一个QTimer
对象和信号连接到槽函数 ;
单次触发的计时器:使用静态函数QTimer::singleShot
,触发某些需要延迟触发的操作或需要在当前当不是现在 (即想要执行,但可能立即执行会产生问题,使用interval
为0
的singleShot
可以实现将其加入函数执行队列中);
废话结束,看源码吧
解析 重复可用的计时器解析 表层解析 首先看看浮于表面的QTimer
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 class QTimer : public QObject{ ... public Q_SLOTS: void start (int msec) ; void start () ; void stop () ; Q_SIGNALS: void timeout () ; protected : void timerEvent (QTimerEvent *) ; ... };
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 void QTimer::start (int msec) { inter = msec; start (); } void QTimer::start () { if (id != INV_TIMER) stop (); nulltimer = (!inter && single); id = QObject::startTimer (inter, Qt::TimerType (type)); } void QTimer::stop () { if (id != INV_TIMER) { QObject::killTimer (id); id = INV_TIMER; } } void QTimer::timerEvent (QTimerEvent *e) { if (e->timerId () == id) { if (single) stop (); emit timeout (QPrivateSignal()) ; } }
从这里可以看出,QTimer的实现就是围绕QObject
中的三个函数:
public static int startTimer(int interval, Qt::TimerType timerType)
;
public static void killTimer(int id)
;
protected virtual void timerEvent(QTimerEvent *)
;
内核解析 当然,这只是表面的,现在进到QObject
中查看更底层的实现:
startTimer 首先来看看startTimer
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 int QObject::startTimer (int interval, Qt::TimerType timerType) { Q_D (QObject); if (Q_UNLIKELY (interval < 0 )) { qWarning ("QObject::startTimer: Timers cannot have negative intervals" ); return 0 ; } auto thisThreadData = d->threadData.loadRelaxed (); if (Q_UNLIKELY (!thisThreadData->hasEventDispatcher ())) { qWarning ("QObject::startTimer: Timers can only be used with threads started with QThread" ); return 0 ; } if (Q_UNLIKELY (thread () != QThread::currentThread ())) { qWarning ("QObject::startTimer: Timers cannot be started from another thread" ); return 0 ; } int timerId = thisThreadData->eventDispatcher.loadRelaxed ()->registerTimer (interval, timerType, this ); if (!d->extraData) d->extraData = new QObjectPrivate::ExtraData; d->extraData->runningTimers.append (timerId); return timerId; }
从上面可以看到,QObject调用了QAbstractEventDispatcher::registerTimer
这个函数,我们层层深入:
1 2 3 4 5 6 int QAbstractEventDispatcher::registerTimer (int interval, Qt::TimerType timerType, QObject *object) { int id = QAbstractEventDispatcherPrivate::allocateTimerId (); registerTimer (id, interval, timerType, object); return id; }
此函数总共有3行,我们每一行进行解析:
第一行 此处调用了QAbstractEventDispatcherPrivate
中allocateTimerId
去获取一个Id
,此函数很简单:
1 2 3 4 int QAbstractEventDispatcherPrivate::allocateTimerId () { return timerIdFreeList ()->next (); }
这里我们在深入了解一下QFreeList;
如果不想深入可以到直接下一个小节然后前翻一点看结论;
这里又涉及到一个新的东西:timerIdFreeList
,这是个什么呢?源码中这个函数的前几行有其定义:
1 2 3 typedef QFreeList<void , QtTimerIdFreeListConstants> QtTimerIdFreeList;Q_GLOBAL_STATIC (QtTimerIdFreeList, timerIdFreeList)
越来越多东西了,不急,我们慢慢进行分析,首先此处简单讲一下QFreeList
:
1 2 3 4 5 6 7 8 9 template <typename T, typename ConstantsType = QFreeListDefaultConstants>class QFreeList { ... public : inline int next (); inline void release (int id) ; ... }
注释中对其的解释翻译后为:
这是一个无锁自由列表的通用实现。使用 next() 获取列表中的下一个自由条目,并在使用完成id后 release(id)。
此版本是模板化的,允许使用 next() 返回的id访问T类型的有效负载。
有效负载由自由列表自动分配和解除分配,但在调用 next()/release() 时不会。
初始化应该在 next() 返回后由需要它的代码完成。
同样,cleanup() 应该在调用 release() 之前发生。
可以使用“void”作为有效载荷类型,在这种情况下,自由列表只包含下一个自由项的索引。
ConstantsType类型默认为上述 QFreeListDefaultConstants。您可以定义自定义的 ConstantsType,请参阅上面的内容了解需要提供的详细信息
总结一下就是调用QFreeList::next
获取一个唯一的id,在使用完毕后调用QFreeList::release
释放
第二行 此处调用的registerTimer(id, interval, timerType, object);
为此类的一个纯虚函数,通过项目中搜索public QAbstractEventDispatcher
我们找到了QEventDispatcherWin32
(源码版本5.7.1,其他版本可能有所不同);
此处我们不再深入聊这个类,只说其重写的registerTimer
函数:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 void QEventDispatcherWin32::registerTimer (int timerId, int interval, Qt::TimerType timerType, QObject *object) { ... ... Q_D (QEventDispatcherWin32) if (d->closingDown) return ; WinTimerInfo *t = new WinTimerInfo; t->dispatcher = this ; t->timerId = timerId; t->interval = interval; t->timerTpye = timerType; t->obj = object; t->inTimerEvent = false ; t->fastTimerId = 0 ; if (d->internalHwnd) d->registerTimer (t); d->timerVec.append (t); d->timerDict.insert (t->timerId, t); }
此处的作用即将上层传入的数据封装到结构体,并在内部窗口存在时注册此结构体,并添加到内部数组和字典当中。
其中注册结构体调用的QEventDispatcherWin32Private::registerTimer
源码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 void QEventDispatcherWin32Private::registerTimer (WinTimerInfo *t) { Q_ASSERT (internalHwnd); Q_Q (QEventDispatcherWin32); int ok = 0 ; calculateNextTimeout (t, qt_msectime ()); uint interval = t->interval; if (interval == 0u ) { QCoreApplication::postEvent (q, new QZeroTimerEvent (t->timerId)); ok = 1 ; } else if ((interval < 20u || t->timerType == Qt::PreciseTimer) && qtimeSetEvent) { ok = t->fastTimerId = qtimeSetEvent (interval, 1 , qt_fast_timer_proc, (DWORD_PTR)t, TIME_CALLBACK_FUNCTION | TIME_PERIODIC | TIME_KILL_SYNCHRONOUS); } if (ok == 0 ) { ok = SetTimer (internalHwnd, t->timerId, interval, 0 ); } if (ok == 0 ) qErrorWarning ("QEventDispatcherWin32::registerTimer: Failed to create a timer" ); }
对于间隔为0
的计时器,实际是创建了一个QZeroTimerEvent
事件;
此函数的关键位置在else if(...)
包含的代码块以及之后如果ok
为0
(即之前操作失败)的情况下的处理;
首先是else if
代码块,当触发间隔小于20
且计时器类型为PreciseTimer
(精确到毫秒)且qtimeSetEvent
存在时,进入代码块;前两个判断条件显而易见,第三个qtimeSetEvent
是什么呢?我们来看一下它的定义和赋值:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 typedef MMRESULT (WINAPI *ptimeSetEvent) (UINT, UINT, LPTIMECALLBACK, DWORD_PTR, UINT) ;static ptimeSetEvent qtimeSetEvent = 0 ;static void resoloveTimerApi () { static bool triedResolve = false ; if (!triedResolve){ ... #if !defined(Q_OS_WINCE) qtimeSetEvent = (ptimeSetEvent)QSystemLibrary::resolve (QLatin1String ("winmm" ), "timeSetEvent" ); ... #else ... #endif } }
上面的操作将系统库中的timeSetEvent
函数指针保存;
timeSetEvent
在MSDN
中解释为:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 MMRESULT timeSetEvent ( UINT uDelay, UINT uResolution, LPTIMECALLBACK lpTimeProc, DWORD_PTR dwUser, UINT fuEvent ) ;
此代码块中对于qtimeSetEvent
调用时的uResolution
参数为qt_fast_timer_proc
,其代码为:
1 2 3 4 5 6 7 8 9 10 11 12 void WINAPI QT_WIN_CALLBACK qt_fast_timer_proc (uint timer, uint , DWORD_PTR user, DWORD_PTR , DWORD_PTR ) { if (!timerId) return ; WinTimerInfo *t = (WinTimerInfo*)user; Q_ASSERT (t); QCoreApplication::postEvent (t->dispatcher, new QTimerEvent (t->timerId)); }
简单来说else if
代码块的作用为创建一个系统提供的高精度定时器;此计时器原理为:通过超时后调用回调函数,回调函数会对回调的参数中所属调度者发出QTimerEvent
(超时)事件
else if
代码块解析完毕,再来看看第一个if(ok == 0)
的代码块,此函数只有一行,作用为在前面创建失败后,调用Windows系统函数SetTimer
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 UINT_PTR SetTimer ( _In_opt_ HWND hWnd, _In_ UINT_PTR nIDEvent, _In_ UINT uElapse, _In_opt_ TIMERPROC lpTimerFunc ) ;
从上方函数调用可见,此处lpTimerFunc
为空,系统会将WM_TIMER
消息发布到应用程序队列;
至此,第二行解析完毕。
第三行 第三行就很简单了,将前一步获取的timerId
返回;
至此,有关startTimer
解析完毕。
killTimer 解析完startTimer
后,来看killTimer
就简单多了:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 void QObject::killTimer (int id) { Q_D (QObject); ... if (id) { int at = d->extraData ? d->extraData->runningTimers.indexOf (id) : -1 ; if (at == -1 ) { ... return ; } if (d->threadData->eventDispatcher.load ()) d->threadData->eventDispatcher.load ()->unregisterTimer (id); d->extraData->runningTimers.remove (at); QAbstractEventDispatcherPrivate::releaseTimerId (id); } }
按照之前一样的分析,首先判断id
存在后,获取索引值;当索引值存在,调用QAbstractEventDispatcher::unregisterTimer
,并移除当前运行计时器数组中对应id
,最后调用QAbstractEventDispatcherPrivate::releaseTimerId
移除自由队列中的id
;
这里重点解析QAbstractEventDispatcher::unregisterTimer
;
QAbstractEventDispatcher::unregisterTimer
与之前的registerTimer
一样,都是纯虚函数,通过找到其在QEventDispatcherWin32
中的实现:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 bool QEventDispatcherWin32::unregisterTimer (int timerId) { ... ... Q_D (QEventDispatcherWin32); if (d->timerVec.isEmpty () || timerId <= 0 ) return false ; WinTimerInfo *t = d->timerDict.value (timerId); if (!t) return false ; d->tiemrDict.remove (t->timerId); t->timerVec.removeAll (t); d->unregisterTimer (t); return true ; }
可以看到,关键的还是在d
指针的unregisterTimer
函数,通过查看其源码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 void QEventDispatcherWin32Private::unregisterTimer (WinTimerInfo *t) { 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 (internamHwnd, t->timerId); } delete t; }
首先,这个函数中出现了三个函数,我们逐个解析:
QCoreApplicationPrivate::removePostedTimerEvent
通过查看其在qcoreapplication_win.cpp
中的实现,其函数作用为删除对应调度者下对应timerId
的计时器,此处不过多赘述;
qtimeKillEvent
与之前qtimeSetEvent
类似,也是通过加载系统库中的timeKillEvent
函数指针,并在此处调用取消指定的定时器计时;
KillTimer
与之前SetTimer
对应,删除SetTimer
设置的计时器;
至此,重复可用的计时器解析完成 。
单次触发的计时器 表层解析 按着之前的方法,由表面层层深入:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 class QTimer : public QObject{ public : static void singleShot (int mesc, const QObject *receiver, const char *member) ; static void singleShot (int mesc, Qt::TimerType timerType, const QObject *receiver, const char *member) ; ... template <typename Func1> static inline void singleShot (int msec, const typename QtPrivate::FunctionPointer<Func1>::Object *receiver, Func1 slot) { singleShot (msec, msec >= 2000 ? Qt::CoarseTimer : Qt::PreciseTimer, receiver, slot) }; ... }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 oid singleShot (int mesc, const QObject *receiver, const char *member) { singleShot (msec, msec >= 2000 ? Qt::CoarseTimer : Qt::PreciseTimer, receiver, member); } void singleShot (int mesc, Qt::TimerType timerType, const QObject *receiver, const char *member) { if (Q_UNLIKELY (msec < 0 ) { ... return ; } if (receiver && member) { if (msec == 0 ) { ... QMetaObject::invokeMethod (const_cast <QObject *>(receiver), methodNmae.constData (), Qt::QueuedConnection); return ; } } (void ) new QSingleShotTimer (mesc, timerType, receiver, member); }
这里又引出了一个类:QSingleShotTimer
,通过查看此类的定义:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 class QSingleShotTimer : public QObject{ ... public : QSingleShotTimer (int msec, Qt::TimerType timerType, const QObject *r, const char *m); QSingleShotTimer (int msec, Qt::TimerType timerType, const QObject *r, QtPrivate::QSlotObjectBase *slotObj); ~QSingleShotTimer (); ... Q_SIGNALS: void timeout () ; protected : void timerEvent (QTimerEvent *) Q_DECL_OVERRIDE ; }
内核解析 这里可以看出来,这个类与QTimer
很相似,同样有timeout
信号和timerEvent
函数:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 QSingleShotTimer (int msec, Qt::TimerType timerType, const QObject *r, const char *m) : QObject (QAbstractEventDispatcher::instance ()) , hasValidReceiver (false ) , slotObj (0 ) { timerId = startTimer (msec, timerType); connect (this , SIGNAL (timeout), r, member); } QSingleShotTimer (int msec, Qt::TimerType timerType, const QObject *r, QtPrivate::QSlotObjectBase *slotObj) : QObject (QAbstractEventDispatcher::instance ()) , hasValidReceiver (false ) , receiver (r) , slotObj (slotObj) { timerId = startTimer (mesc, timerType); if (r && thread != r->thread ()) { connect (QCoreApplication::instance (), &QCoreApplication::aboutToQuit, this , &QObject::deleteLater); setParent (0 ); moveToThread (r->thread); } } ~QSingleShotTimer () { if (timerId > 0 ) killTimer (timerId); if (slotObj) slotObj->destoryIfLastRef (); } void QSingleShotTimer::timerEvent (QTimerEvent *) { if (timerId > 0 ) killTimer (timerId); timerId = -1 ; if (slotObj) { if (Q_LIKELY (!receiver.isNull () || !hasValidReceiver) { void *args[1 ] = {0 }; slotObj->call (const_cast <QObject *>(receiver.data ()), args); } } else { emit timeout (); } qDeleteInEventHandler (this ); }
两个构造函数都调用了QObject::startTimer
创建计时器,有关这一个不过多赘述,请看上面关于startTimer的解析 ;
有关析构函数,依旧是调用了QObject::killTimer
,也不过多赘述,请看上面关于killTimer的解析 ;
再来看事件处理,首先依旧是删除对应计时器;再进行slotObj
的判断:
如果slotObj
存在,则为第二种构造函数,传入的为函数指针,直接通过QSlotObjectBase::call
调用目标函数;
如果不存在,则为第一种构造函数,因为之前进行了槽链接,直接发出timeout
信号即可;
完成上述操作后删除自身。
至此,单次触发的计时器解析完毕
总结 Qt的计时器QTimer
的底层实现原理是根据触发间隔分类:
触发间隔为0
时
使用singleShot
会将函数加入调用队列(QueuedConnection
方式的QObject::invokeMethod
),用Qt
底层机制去实现;
使用普通QTimer
对象时,会使用postEvent
派发一个QZeroTimerEvent
到QAbstractEventDispatcher
自身,再通过事件处理调用QObject::SendEvent
派发到QTimerEvent
对应的对象当中;
触发间隔不为0
时调用操作系统计时器,且再Windows
平台会根据触发间隔的长短选择性能不一的计时器。
我们在使用时往往不会去在意其实现,但往往了解其实现后会更容易去使用它。
学吧,学无止境!