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");
return0;
}
if (Q_UNLIKELY(thread() != QThread::currentThread())) {
qWarning("QObject::startTimer: Timers cannot be started from another thread");
return0;
}
// 调⽤对象关联线程的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<TimerInfo> list;,其中的结构体TimerInfo除了⼀个内联函数,成员如下:
timeout on t2 timerint 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 {
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);
return0;
}
接着:
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:"<<QThread::currentThread();
timer = new QTimer();
connect(timer,SIGNAL(timeout()),this,SLOT(testTimer()));
timer->start(2000);
}
void MyObj::testTimer()
{
QString str = QString::number(qrand()%100);
qDebug()<<"test timer:"<<str;
emit toLine(str);
}
在次线程中创建和开启了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();
与前⼀篇的代码⼏乎相同,不再解释。
参考:

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。