Qt源码阅读(六) ⏱️QTimer
Qt源码阅读(六) ⏱️QTimer
Qt 为我们提供了一个非常实用的定时器(QTimer),而在标准库中却没有类似的通用定时器。网络上有很多文章教你如何实现一个定时器,但本着就近原则,今天我们将深入阅读 Qt 中 QTimer 的源码,探索其内部机制。此前,我们已经一起阅读了 Qt 核心部分的一些源码,今天我们将继续这一旅程🕵️,揭开 Qt 定时器背后的秘密。
🚀源码探索
➕定时器的注册
我们的旅程从QTimer的 start
函数开始:
void QTimer::start()
{
Q_D(QTimer);
if (d->id != QTimerPrivate::INV_TIMER) // stop running timer
stop();
d->id = QObject::startTimer(std::chrono::milliseconds{d->inter}, d->type);
d->isActiveData.notify();
}
可以看到,这个函数主要是调用了QObject::startTimer。
int QObject::startTimer(std::chrono::milliseconds interval, Qt::TimerType timerType)
{
Q_D(QObject);
using namespace std::chrono_literals;
// ...省略
auto dispatcher = thisThreadData->eventDispatcher.loadRelaxed();
int timerId = dispatcher->registerTimer(interval.count(), timerType, this);
d->ensureExtraData();
d->extraData->runningTimers.append(timerId);
return timerId;
}
QObject::startTimer
主要通过调用QAbstractEventDispatcher
的registerTimer
函数来注册一个定时器,这表明 QTimer
的底层实现与操作系统紧密相关。例如在Windows系统下,对应的类就是 QEventDispatcherWin32
。
void QEventDispatcherWin32Private::registerTimer(WinTimerInfo *t)
{
Q_ASSERT(internalHwnd);
Q_Q(QEventDispatcherWin32);
bool ok = false;
ULONG tolerance = calculateNextTimeout(t, qt_msectime());
uint interval = t->interval;
if (interval == 0u) {
// optimization for single-shot-zero-timer
QCoreApplication::postEvent(q, new QZeroTimerEvent(t->timerId));
ok = true;
} else if (tolerance == TIMERV_DEFAULT_COALESCING) {
// 3/2016: Although MSDN states timeSetEvent() is deprecated, the function
// is still deemed to be the most reliable precision timer.
t->fastTimerId = timeSetEvent(interval, 1, qt_fast_timer_proc, DWORD_PTR(t),
TIME_CALLBACK_FUNCTION | TIME_PERIODIC | TIME_KILL_SYNCHRONOUS);
ok = t->fastTimerId;
}
// 省略...
}
在这段代码中,我们可以看到如果定时器的interval为0的话,就post一个QZerorTimerEvent事件。如果不为0就调用timeSetEvent创建一个定时器,函数原型为:
MMRESULT timeSetEvent(
UINT uDelay,
UINT uResolution,
LPTIMECALLBACK lpTimeProc,
DWORD_PTR dwUser,
UINT fuEvent
);
尽管在微软文档中,此函数被标记为已弃用,但在Qt的源码中,能找到这样一条注释🤔:
// 3/2016: Although MSDN states timeSetEvent() is deprecated, the function
// is still deemed to be the most reliable precision timer.
具体文档参考:timeSetEvent function (Windows)
其中:
LPTIMECALLBACK
:为定时器事件的回调函数。Qt 在这一部分是直接调用qt_fast_time_proc
函数来进行处理:
// This function is called by a workerthread
void WINAPI QT_WIN_CALLBACK qt_fast_timer_proc(uint timerId, uint /*reserved*/, DWORD_PTR user, DWORD_PTR /*reserved*/, DWORD_PTR /*reserved*/)
{
if (!timerId) // sanity check
return;
auto t = reinterpret_cast<WinTimerInfo*>(user);
Q_ASSERT(t);
QCoreApplication::postEvent(t->dispatcher, new QTimerEvent(t->timerId));
}
可以看到这个函数就是简单的 post 了一个 QTimerEvent 到对应的 event dispatcher (事件分发器)。
bool QEventDispatcherWin32::event(QEvent *e)
{
Q_D(QEventDispatcherWin32);
switch (e->type()) {
case 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);
// timer could have been removed
if (t->timerId == -1) {
delete t;
} else {
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;
}
case QEvent::Timer:
d->sendTimerEvent(static_cast<const QTimerEvent*>(e)->timerId());
break;
default:
break;
}
return QAbstractEventDispatcher::event(e);
}
有关于ZeroTimerEvent的待会咱们再来探索。普通的定时器事件,调用的是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;
// recalculate next emission
calculateNextTimeout(t, qt_msectime());
QTimerEvent e(t->timerId);
QCoreApplication::sendEvent(t->obj, &e);
// timer could have been removed
if (t->timerId == -1) {
delete t;
} else {
t->inTimerEvent = false;
}
}
}
这个函数先会调用calculateNextTimeout
计算下一个超时时间,对于普通计时器,就是简单的把当前时间和间隔进行相加。然后再通过sendEvent
发送一个QTimerEvent到指定的对象。
static ULONG calculateNextTimeout(WinTimerInfo *t, quint64 currentTime)
{
uint interval = t->interval;
ULONG tolerance = TIMERV_DEFAULT_COALESCING;
switch (t->timerType) {
case Qt::PreciseTimer:
// high precision timer is based on millisecond precision
// so no adjustment is necessary
break;
case Qt::CoarseTimer:
// this timer has up to 5% coarseness
// so our boundaries are 20 ms and 20 s
// below 20 ms, 5% inaccuracy is below 1 ms, so we convert to high precision
// above 20 s, 5% inaccuracy is above 1 s, so we convert to VeryCoarseTimer
if (interval >= 20000) {
t->timerType = Qt::VeryCoarseTimer;
} else if (interval <= 20) {
// no adjustment necessary
t->timerType = Qt::PreciseTimer;
break;
} else {
tolerance = interval / 20;
break;
}
Q_FALLTHROUGH();
case Qt::VeryCoarseTimer:
// the very coarse timer is based on full second precision,
// so we round to closest second (but never to zero)
tolerance = 1000;
if (interval < 1000)
interval = 1000;
else
interval = (interval + 500) / 1000 * 1000;
currentTime = currentTime / 1000 * 1000;
break;
}
t->interval = interval;
t->timeout = currentTime + interval;
return tolerance;
}
➖定时器的移除
前面的旅途,我们跟着定时器,对它的启动有了一个简单的了解。我们旅途的下一站则是定时器的终点——定时器的停止。和启动一样,stop
函数同样是调用了QObject的函数:QObject::killTimer
void QTimer::stop()
{
Q_D(QTimer);
if (d->id != QTimerPrivate::INV_TIMER) {
QObject::killTimer(d->id);
d->id = QTimerPrivate::INV_TIMER;
d->isActiveData.notify();
}
}
killTimer
函数接收一个 ID 参数,这个 ID 是之前调用registerTimer
返回的定时器 ID。根据这个 ID,我们可以找到对应的定时器信息。
void QObject::killTimer(int id)
{
Q_D(QObject);
if (Q_UNLIKELY(thread() != QThread::currentThread())) {
qWarning("QObject::killTimer: Timers cannot be stopped from another thread");
return;
}
if (id) {
int at = d->extraData ? d->extraData->runningTimers.indexOf(id) : -1;
if (at == -1) {
// timer isn't owned by this object
qWarning("QObject::killTimer(): Error: timer id %d is not valid for object %p (%s, %ls), timer has not been killed",
id,
this,
metaObject()->className(),
qUtf16Printable(objectName()));
return;
}
auto thisThreadData = d->threadData.loadRelaxed();
if (thisThreadData->hasEventDispatcher())
thisThreadData->eventDispatcher.loadRelaxed()->unregisterTimer(id);
d->extraData->runningTimers.remove(at);
QAbstractEventDispatcherPrivate::releaseTimerId(id);
}
}
同样地,以 Windows 为例:killTimer 中调用的是 QEventDispatcherWin32::unregisterTimer
函数。
bool QEventDispatcherWin32::unregisterTimer(int timerId)
{
#ifndef QT_NO_DEBUG
if (timerId < 1) {
qWarning("QEventDispatcherWin32::unregisterTimer: invalid argument");
return false;
}
if (thread() != QThread::currentThread()) {
qWarning("QEventDispatcherWin32::unregisterTimer: timers cannot be stopped from another thread");
return false;
}
#endif
Q_D(QEventDispatcherWin32);
WinTimerInfo *t = d->timerDict.take(timerId);
if (!t)
return false;
d->unregisterTimer(t);
return true;
}
unregisterTimer
函数首先从维护的定时器容器中取出指定 ID 的定时器,然后调用实际的注销函数处理。
void QEventDispatcherWin32Private::unregisterTimer(WinTimerInfo *t)
{
if (t->interval == 0) {
QCoreApplicationPrivate::removePostedTimerEvent(t->dispatcher, t->timerId);
} else if (t->fastTimerId != 0) {
timeKillEvent(t->fastTimerId);
QCoreApplicationPrivate::removePostedTimerEvent(t->dispatcher, t->timerId);
} else {
KillTimer(internalHwnd, t->timerId);
}
t->timerId = -1;
if (!t->inTimerEvent)
delete t;
}
这个函数根据定时器设置的间隔来决定具体的操作:
- 如果定时器间隔为0,就直接移除这个定时器已经抛出的定时器事件。
- 如果定时器间隔不为 0,则先调用 WinAPI
timeKillEvent
注销定时器,再移除已发布的定时器事件。
void QCoreApplicationPrivate::removePostedTimerEvent(QObject *object, int timerId)
{
QThreadData *data = object->d_func()->threadData.loadRelaxed();
const auto locker = qt_scoped_lock(data->postEventList.mutex);
if (data->postEventList.size() == 0)
return;
for (int i = 0; i < data->postEventList.size(); ++i) {
const QPostEvent &pe = data->postEventList.at(i);
if (pe.receiver == object
&& pe.event
&& (pe.event->type() == QEvent::Timer || pe.event->type() == QEvent::ZeroTimerEvent)
&& static_cast<QTimerEvent *>(pe.event)->timerId() == timerId) {
--pe.receiver->d_func()->postedEvents;
pe.event->m_posted = false;
delete pe.event;
const_cast<QPostEvent &>(pe).event = 0;
return;
}
}
}
在将定时器id设置为-1,此时,在前面我们所看到的event
函数中,就会**因为定时器的id为-1,而将定时器的信息(WinTimeInfo*)释放掉。**防止处理以删除的定时器的事件。
🤓QTimer中interval设置为0的意义
From then on, processOneThing() will be called repeatedly. It should be written in such a way that it always returns quickly (typically after processing one data item) so that Qt can deliver events to the user interface and stop the timer as soon as it has done all its work. This is the traditional way of implementing heavy work in GUI applications, but as multithreading is nowadays becoming available on more and more platforms, we expect that zero-millisecond QTimer objects will gradually be replaced by QThreads
在Qt中,将定时器的interval设置为0,通常用来表示“尽可能快地执行一次”,但仍然保持在应用程序的事件循环的控制之下。这对于某些需要立即响应但又不想直接在当前函数中执行的任务来说非常有用。且定时器事件也并不会无限循环的触发,而是会在下次事件循环后触发。
咱们在源码中也可以看到,如果一个事件的间隔是0,那么其会在事件分发器中,不断的触发。直到定时器的id设置为-1,也就是定时器注销或者停止的时候。
bool QEventDispatcherWin32::event(QEvent *e)
{
Q_D(QEventDispatcherWin32);
switch (e->type()) {
case 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);
// timer could have been removed
if (t->timerId == -1) {
delete t;
} else {
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;
}
// 省略...
}
return QAbstractEventDispatcher::event(e);
}
✍️总结
通过这些步骤,我们可以清楚地理解 QTimer
在不同情况下的注册和注销过程。希望这段解析能够帮助你更好地理解和使用 Qt 的定时器功能。
- 注册、事件的处理调用图
原文地址:https://blog.csdn.net/qq_44723937/article/details/144329307
免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!