QTimer与事件循环和多线程

定时器的源码分析

startTimer返回定时器的ID,在定时时间到了后,收到一个QTimerEvent,并覆盖虚函数timerEvent进行处理,该QTimerEvent包括了定时器ID

看QTimer的源码就明白了:

QObject::startTimer()
{
        if (Q_UNLIKELY(!d->threadData->eventDispatcher.load())) {
        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;
    }
    // 调用对象关联线程的eventDispatcher来注册定时器,killTimer中是unregisterTimer
    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;
}

event dispatcher维护每个QObject对象关联的定时器的列表,再看registerTimer的源码:

 void QEventDispatcherWin32::registerTimer(int timerId, int interval, Qt::TimerType timerType, QObject *object)
{
    if (timerId < 1 || interval < 0 || !object) {
        qWarning("QEventDispatcherWin32::registerTimer: invalid arguments");
        return;
    } else if (object->thread() != thread() || thread() != QThread::currentThread()) {
        // 又判断是不是跟event dispatcher同一线程
        qWarning("QEventDispatcherWin32::registerTimer: timers cannot be started from another thread");
        return;
    }

    Q_D(QEventDispatcherWin32);
    // exiting ... do  not  register  new timers (QCoreApplication::closingDown()  is  set  too late  to  be  used  here)
    if (d->closingDown)
        return;
        // 分配计时器ID,间隔,类型
    WinTimerInfo *t = new WinTimerInfo;
    t->dispatcher = this;
    t->timerId  = timerId;
    t->interval = interval;
    t->timerType = timerType;
    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
}

QAbstractEventDispatcher::registeredTimers 可被用来查询QObject对象所关联的定时器的列表。即QList list;,其中的结构体TimerInfo除了一个内联函数,成员如下:

int timerId;
int interval;
Qt::TimerType   timerType;

    enum   TimerType {      //下面的函数会根据不同类型做不同处理
        PreciseTimer,
        CoarseTimer,
        VeryCoarseTimer
    };

接着看内部实现类的同名函数,这次有点复杂:

void QEventDispatcherWin32Private::registerTimer(WinTimerInfo *t)
{
    //参数是 internal window handle used for socketnotifiers/timers/etc
    Q_ASSERT(internalHwnd);
    Q_Q(QEventDispatcherWin32);

    bool ok = false;
    calculateNextTimeout(t, qt_msectime());
    uint   interval = t->interval;
    if (interval == 0u) {
        //对于时间间隔为0的timer,不启动系统的计时器,异步发送QZeroTimerEvent 事件到自身来处理
        QCoreApplication::postEvent(q, new QZeroTimerEvent(t->timerId));
        ok = true;
    } else if (interval < 20u || t->timerType == Qt::PreciseTimer) {
        //间隔小于20ms或者类型时Precise,首先用多媒体定时器(fast 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) {
        // 如果多媒体定时器不可用,或者是(Very)CoarseTimers类型,使用SetTimer发送
        WM_TIMER消息到回调函数中并作为QEvent::Timer发送到QObject。
        ok = SetTimer(internalHwnd, t->timerId, interval, 0);
    }
    if (!ok)        // 再失败就报错
        qErrnoWarning("QEventDispatcherWin32::registerTimer: Failed to create a timer");
}

代码根据时间间隔给出了三种处理方式。
1. 间隔为0,不再解释
2. timeSetEvent,它接受一个回调函数qt_fast_timer_proc,到期时它会被调用并在一个独立的线程中被调用。回调函数 post 一个消息到派发器,派发器将获得该消息并作为QEvent::Timer发送,看源码:

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;
    WinTimerInfo *t = (WinTimerInfo*)user;
    Q_ASSERT(t);
    QCoreApplication::postEvent(t->dispatcher, new QTimerEvent(t->timerId));
}
  1. 时间间隔大于20毫秒,它使用 SetTimer发送WM_TIMER消息到回调函数qt_internal_proc,并作为QEvent::Timer发送到QObject。回调函数中的部分代码:
else if (message == WM_TIMER) {
        Q_ASSERT(d != 0);
        d->sendTimerEvent(wp);
        return 0;
    }

接着找:

QEventDispatcherWin32Private::sendTimerEvent
{
    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);
        //同步派发了QTimerEvent事件
        QCoreApplication::sendEvent(t->obj, &e);
        . . . . . . 
    }
}

以上发送的QEvent::Timer事件,处理都在QObject::timerEvent当中。
timerEvent() 中发射 timeout() 信号:

void QTimer::timerEvent(QTimerEvent *e)
{
    if (e->timerId() == id) {
        if (single)
            stop();
        emit timeout(QPrivateSignal());
    }
}

定时器ID

定时器ID是按某分配算法得到的,一定是唯一的(甚至在跨线程的情况下)。当一个QObject从一个线程移动到另一个时(moveToThread),它的定时器也会随它一起移动。移动定时器是一个简单从旧线程的派发器注销计时器并在在新线程的派发器中注册的问题。如果定时器的ID是不是唯一的,定时器ID可能会与新线程中现有的定时器发生冲突。

moveToThread的三大任务中的第三条是解除在当前线程中的timer注册,在目标线程中重新注册。原因是第1条:函数通过sendEvent()派发 QEvent::ThreadChange事件,在QObject::event中处理。看event函数中的部分源码:

    case QEvent::ThreadChange: {
        Q_D(QObject);
        QThreadData *threadData = d->threadData;
        QAbstractEventDispatcher *eventDispatcher = threadData->eventDispatcher.load();
        if (eventDispatcher) {
            QList<QAbstractEventDispatcher::TimerInfo> timers = eventDispatcher->registeredTimers(this);
            if (!timers.isEmpty()) {
                // do  not  to  release  our  timer  ids  back  to  the pool (since the timer ids are moving to  a  new  thread).
                // 解除注册
                eventDispatcher->unregisterTimers(this);
                QMetaObject::invokeMethod(this, "_q_reregisterTimers", Qt::QueuedConnection,
                                          Q_ARG(void*, (new QList<QAbstractEventDispatcher::TimerInfo>(timers))));      }       }
        break;
    }

又异步调用了函数_q_reregisterTimers,其中eventDispatcher->registerTimer(ti.timerId, ti.interval, ti.timerType, q);实现了重新注册。

多线程中使用定时器

从上面的分析可以看出,多线程中使用定时器时,必须在同一个线程里开始和停止定时器,也就是只有在创建定时器的线程里才能接受到timeout()信号,一般是次线程的run函数。否则就会在startTimer和registerTimer中报错。
另一种方法,是将定时器和工作类都移到某个子线程。

现在看这样的程序,在次线程开启定时器,每秒产生一个随机数,然后在主线程的文本框中添加一个个的随机数。

class MyObj : public QObject
{
    Q_OBJECT
public:
    MyObj();
signals:
    void toLine(QString line);
private slots:
    void doWork();
    void testTimer();
private:
    QTimer* timer;
};

void MyObj::doWork()
{
    qDebug()<<"timer thread:"<new QTimer();
    connect(timer,SIGNAL(timeout()),this,SLOT(testTimer()));
    timer->start(2000);
}

void MyObj::testTimer()
{
    QString str = QString::number(qrand()%100);
    qDebug()<<"test timer:"<

在次线程中创建和开启了timer

主线程部分的代码:

t = new QThread();
obj = new MyObj();
obj->moveToThread(t);
qDebug()<<"main thread:"<<QThread::currentThread();
connect(t,SIGNAL(started()), obj, SLOT(doWork()), Qt::QueuedConnection);
connect(obj,SIGNAL(toLine(QString)),this,SLOT(appendText(QString) ),Qt::QueuedConnection );
connect(t,SIGNAL(finished()), obj, SLOT(deleteLater()) );
t->start();

与前一篇的代码几乎相同,不再解释。

参考:Qt官方对Timer的解释

你可能感兴趣的:(Qt)