QTimer源码分析(以Windows下实现为例)
起源
在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
QTimer源码分析(以Windows下实现为例)的更多相关文章
- SpringAOP使用及源码分析(SpringBoot下)
一.SpringAOP应用 先搭建一个SpringBoot项目 <?xml version="1.0" encoding="UTF-8"?> < ...
- JDK源码分析(三)——HashMap 下(基于JDK8)
目录 概述 内部字段及构造方法 哈希值与索引计算 存储元素 扩容 删除元素 查找元素 总结 概述 在上文我们基于JDK7分析了HashMap的实现源码,介绍了HashMap的加载因子loadFac ...
- [nginx] nginx源码分析--proxy模式下nginx的自动重定向auto_redirect
描述 我们配置了一个proxy模式下的nginx, upstream backend-test { server ; } server { listen ; location = /nginx/hww ...
- JVM源码分析之JDK8下的僵尸(无法回收)类加载器[z]
[z]http://lovestblog.cn/blog/2016/04/24/classloader-unload/ 概述 这篇文章基于最近在排查的一个问题,花了我们团队不少时间来排查这个问题,现象 ...
- Netty服务端的启动源码分析
ServerBootstrap的构造: public class ServerBootstrap extends AbstractBootstrap<ServerBootstrap, Serve ...
- Android应用层View绘制流程与源码分析
1 背景 还记得前面<Android应用setContentView与LayoutInflater加载解析机制源码分析>这篇文章吗?我们有分析到Activity中界面加载显示的基本流程原 ...
- 集合之ArrayList(含JDK1.8源码分析)
一.ArrayList的数据结构 ArrayList底层的数据结构就是数组,数组元素类型为Object类型,即可以存放所有类型数据.我们对ArrayList类的实例的所有的操作(增删改查等),其底层都 ...
- IntentService使用以及源码分析
一 概述 我们知道,在Android开发中,遇到耗时的任务操作时,都是放到子线程去做,或者放到Service中去做,在Service中开一个子线程来执行耗时操作. 那么,在Service里面我们需要自 ...
- 7.Java集合-Arrays类实现原理及源码分析
Java集合---Arrays类源码解析 转自:http://www.cnblogs.com/ITtangtang/p/3948765.html 一.Arrays.sort()数组排序 Java A ...
随机推荐
- jquery的$(document).ready()和onload的加载顺序
最近在改一个嵌入在frame中的页面的时候,使用了jquery做效果,而页面本身也绑定了onload事件.改完后,Firefox下测试正常流畅,IE下就要等个十几秒jquery的效果才出现,黄花菜都凉 ...
- IOC学习
控制反转(Inversion of Control,英文缩写为IoC)是一个重要的面向对象编程的法则来削减计算机程序的耦合问题,也是轻量级的Spring框架的核心. 控制反转一般分为两种类型,依赖注入 ...
- ActiveMQ之selector的用法
前面的例子中创建一个消息消费者使用的是: sesssion.createConsumer(destination) 另外,还提供了另一种方式: sesssion.createConsumer(dest ...
- ActiveMQ之Queue
Queue实现的是点到点模型,在以下的例子中,启动2个消费者共同监听一个Queue,然后循环给这个Queue发送多个消息. 代码如下: public class QueueTest { /** * @ ...
- php中使用linux命令四大步骤
一.PHP中调用外部命令介绍在PHP中调用外部命令,可以用,1>调用专门函数.2>反引号.3>popen()函数打开进程,三种方法来实现: 方法一:用PHP提供的专门函数(四个):P ...
- eclipse增加浏览器chrome
1.安装完了google chrome游览器后,如何让eclipse直接用chrome打开jsp 2.添加到eclipse环境中即可, Window(菜单) -- preferences 增加成功后, ...
- Nginx模块开发1_明白自定义模块的编译流程
自定义模块的编译流程 --add-module参数 configure使用--add-module参数指定添加模块目录. config脚本 由--add-module指定的目录保存为$ngx-addo ...
- iOS 进阶 第五天(0330)
0330 cell的一些常见属性 设置cell右边指示器的类型 设置cell右边指示器的view cell的backgroundView和selectedBackgroundView cell的bac ...
- Oracle 存储过程实例2
--创建存储过程 CREATE OR REPLACE PROCEDURE xxxxxxxxxxx_p ( --参数IN表示输入参数,OUT表示输入参数,类型可以使用任意Oracle中的合法类型. is ...
- Careercup - Facebook面试题 - 23594662
2014-05-02 03:19 题目链接 原题: Given a sequence of numbers A() ..A(n), find the continuous subsequenceA(i ...