Qt源码阅读(六) ⏱️QTimer

Qt源码阅读(六) ⏱️QTimer

Qt 为我们提供了一个非常实用的定时器(QTimer),而在标准库中却没有类似的通用定时器。网络上有很多文章教你如何实现一个定时器,但本着就近原则,今天我们将深入阅读 Qt 中 QTimer 的源码,探索其内部机制。此前,我们已经一起阅读了 Qt 核心部分的一些源码,今天我们将继续这一旅程🕵️,揭开 Qt 定时器背后的秘密。

🚀源码探索

➕定时器的注册

我们的旅程从QTimer的 start 函数开始:

cpp 复制代码
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

cpp 复制代码
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主要通过调用QAbstractEventDispatcherregisterTimer函数来注册一个定时器,这表明 QTimer的底层实现与操作系统紧密相关。例如在Windows系统下,对应的类就是 QEventDispatcherWin32

cpp 复制代码
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创建一个定时器,函数原型为:

cpp 复制代码
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函数来进行处理:

cpp 复制代码
// 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 (事件分发器)。

cpp 复制代码
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函数:

cpp 复制代码
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到指定的对象。

cpp 复制代码
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

cpp 复制代码
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,我们可以找到对应的定时器信息。

cpp 复制代码
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 函数。

cpp 复制代码
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 的定时器,然后调用实际的注销函数处理。

cpp 复制代码
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 注销定时器,再移除已发布的定时器事件。
cpp 复制代码
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,也就是定时器注销或者停止的时候。

cpp 复制代码
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 的定时器功能。

  • 注册、事件的处理调用图
相关推荐
MSTcheng.9 分钟前
C语言操作符(上)
c语言·开发语言
DevOpsDojo16 分钟前
HTML语言的数据结构
开发语言·后端·golang
懒大王爱吃狼18 分钟前
Python绘制数据地图-MovingPandas
开发语言·python·信息可视化·python基础·python学习
数据小小爬虫21 分钟前
如何使用Python爬虫按关键字搜索AliExpress商品:代码示例与实践指南
开发语言·爬虫·python
好一点,更好一点37 分钟前
systemC示例
开发语言·c++·算法
不爱学英文的码字机器39 分钟前
[操作系统] 环境变量详解
开发语言·javascript·ecmascript
martian66544 分钟前
第17篇:python进阶:详解数据分析与处理
开发语言·python
五味香1 小时前
Java学习,查找List最大最小值
android·java·开发语言·python·学习·golang·kotlin
时韵瑶1 小时前
Scala语言的云计算
开发语言·后端·golang
卷卷的小趴菜学编程1 小时前
c++之List容器的模拟实现
服务器·c语言·开发语言·数据结构·c++·算法·list