zoukankan      html  css  js  c++  java
  • QTimer源码分析(以Windows下实现为例)

     

    QTimer源码分析(以Windows下实现为例)

    分类: Qt

    起源

    在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?)。

    • 对于间隔不为零的timer,调用系统提供的计时器,然后等待响应系统的计时器事件
    • 对于间隔为零的timer,Qt自己进行了特殊处理。
      • 当使用静态函数QTimer::singleShot时,实际上直接调用了QueuedConnection方式的 QMetaObject::invokeMethod()

      • 当启动间隔为零的QTimer时,实际上先postEvent()派发了一个 QZeroTimerEvent到QAbstractEventDiapatch自身。在该事件的处理中,再通过sendEvent() 派发 QTimerEvent事件到对象中

    一段废话,作为正文引子

    每当需要一个计时器时,我们很容易想到QTimer,

    • 创建QTimer对象
    • 连接它的信号到我们的槽函数。

    如果看Qt的Manual,我们还会注意到QBasicTimer和QTimeLine这两个类,也可起到计时的作用。那么这些之间那个最为根本呢?

    所有这些都归结到QObject的3个成员函数中:

    • 在派生类中覆盖timerEvent()函数,进行处理
    • 通过startTimer()开启计时器
    • 通过killTimer() 结束

    下面我们看看QTimer的计时事件是如何一步一步和系统提供的计时器(优先使用多媒体计时器,其次是普通的计时器)联系起来的

    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 *);
    ...
    };

    大家应该想得到了(就不贴代码了):

    • start() 调用 QObject::startTimer()
    • stop() 调用 QObject::killTimer()
    • timerEvent() 中发射信号 timeout()

    QTimer::singleShot()

    这是一个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 事件。

    QObject

    • 回归正题,看QObject的 startTimer和killTimer做了什么:

     

    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了。

    QAbstractEventDispatcher

    我们以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;
    }

    呵呵,这段挺复杂的:

    • 对于时间间隔为0的timer,实际上并没有启动系统的计时器,而是创建了一个QZeroTimerEvent 事件
    • 对普通的timer,首先尝试启用多媒体定时器。若失败,则使用传统的SetTimer 和 KillTimer

    非0的timer

    我们先看看对正常的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

    我们前面说了,对于间隔为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进行什么处理了吧?

    • 通过 QCoreApplication 的 sendEvent 派发出 QTimerEvent 事件
    • 同时 产生一个新的 QZeroTimerEvent 事件,放入事件队列中

    参考:http://blog.csdn.net/dbzhang800/article/details/6321621

  • 相关阅读:
    DHCP DHCPv6
    DHCPv6协议
    IPv6邻居发现协议
    CentOS下禁止防火墙
    centOS下更新yum源
    centOS下yum报错
    Flink+Kafka整合的实例
    Flink基本概念
    Ubuntu16.04下配置ssh免密登录
    Zookeeper+Kafka的单节点配置
  • 原文地址:https://www.cnblogs.com/findumars/p/4227399.html
Copyright © 2011-2022 走看看