前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >【Qt源码笔记】关于 QTimer 在 Windows 下实现的杂谈

【Qt源码笔记】关于 QTimer 在 Windows 下实现的杂谈

作者头像
Harper
发布2021-07-27 09:56:55
1.3K0
发布2021-07-27 09:56:55
举报
文章被收录于专栏:Harper的碎碎念Harper的碎碎念

关于 QTimer 的具体实现,翻看源码源于一次面试经历。被问到 QTimer 的问题,我随口说了一句:Windows 平台下是用 Windows API 实现的,然后便引起了怀疑,不过我据理力争,便也作罢。所以回来之后,就又确认了一下。

要找这个佐证就必然要从start()方法顺藤摸瓜。

代码语言:javascript
复制
void QTimer::start()
{
    if (id != INV_TIMER)    // stop running timer
        stop();
    nulltimer = (!inter && single);
    id = QObject::startTimer(inter, Qt::TimerType(type));
}

事实证明还是走到了QObject中。继续顺藤摸瓜。

代码语言:javascript
复制
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;
    }
    if (Q_UNLIKELY(!d->threadData->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 = d->threadData->eventDispatcher.load()->registerTimer(interval, timerType, this);
    if (!d->extraData)
        d->extraData = new QObjectPrivate::ExtraData;
    d->extraData->runningTimers.append(timerId);
    return timerId;
}

这里可以很清楚的看到registerTimer()方法。不过到这里就可以很快找到方法的位置。eventDispatcher让我直接找到 qeventdispatcher_win.cpp。果然在这中间,发现了想要找的东西。

代码语言:javascript
复制
void QEventDispatcherWin32Private::registerTimer(WinTimerInfo *t)
{
    Q_ASSERT(internalHwnd);
    Q_Q(QEventDispatcherWin32);
    bool ok = false;
    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 (interval < 20u || t->timerType == Qt::PreciseTimer) {
        // 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;
    }
    if (!ok) {
        // user normal timers for (Very)CoarseTimers, or if no more multimedia timers available
        ok = SetTimer(internalHwnd, t->timerId, interval, 0);
    }
    if (!ok)
        qErrnoWarning("QEventDispatcherWin32::registerTimer: Failed to create a timer");
}

熟悉的 Windows API 就被发现了。如果当初我能记得 QObject 里边的调用细节,或许应该就在面试的让对方少些疑问了。

其实看到这里的逻辑,会发现一些 Qt 的小操作。

  • 一个间隔为 0 的定时器,Qt 只会发一个事件放到事件队列,不涉及系统 API 调用。
  • 间隔 20ms 以下,会调用timeSetEvent。但是这里有两个隐患:1. timeSetEvent是一个已经废弃的 API;2. timeSetEvent虽然精度高,但是同一个进程开 16 个之后就会失败,这是一个致命伤,想避开这个问题,就要用最新的 CreateTimerQueueTimer,由此可见这里是偷懒了。
  • 除此之外的计时器会调用 SetTimer

在这里,会很自然的想到,Timer 的设置是成对出现的,也就是KillTimertimeKillEvent这种调用。

通过对 QTimer 机制的理解,不难想到目标代码应该从 timeEvent 查起。按图索骥,stop()。最终发现这个:

代码语言:javascript
复制
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 if (internalHwnd) {
        KillTimer(internalHwnd, t->timerId);
    }
    t->timerId = -1;
    if (!t->inTimerEvent)
        delete t;
}

Qt 对 Windows API 的基本调用还是可信赖的,但是就是开发人员懒了一点。

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2018-08-05,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档