Qt wiki will be updated on October 12th 2023 starting at 11:30 AM (EEST) and the maintenance will last around 2-3 hours. During the maintenance the site will be unavailable.
Timers/zh
This article may require cleanup to meet the Qt Wiki's quality standards. Reason: Auto-imported from ExpressionEngine. Please improve this article if you can. Remove the {{cleanup}} tag and add this page to Updated pages list after it's clean. |
简体中文 English [toc align_right="yes" depth="2"]
定时器的 API
Qt 提供了两套 计时器的 API
- QObject::startTimer - 创建一个由QObject的任意子类使用的循环定时器,并返回定时器的ID。当定时时间到时它会收到一个 QEvent::Timer,可以通过覆盖QObject::timerEvent(QTimerEvent*)进行处理。该QTimerEvent参数包含定时器ID,当QObject使用多个定时器时可以进行匹配选择。 QObject::killTimer(id) 可用于停止和解除计时器。
- QTimer - QTimer 是一个当定时时间到时发射 timeout() 信号的 QObject。它简单地使用 QObject::startTimer() 并在事件处理器 QObject::timerEvent() 中发射 timeout() 信号。
QAbstractEventDispatcher
QtEventProcessing 提供了平台原生事件如何被Qt处理的概述信息。
QAbstractEventDispatcher 是事件/消息泵的接口。Qt创建的每一个线程中都存在一个事件派发器(event dispatcher)。为实现定时器它要求 registerTimer 和 unregisterTimer 必须被实现。
它有两个版本的 registerTimer (一个虚函数一个非虚)。两个版本都接受一个定时器所关联的QObject作为参数。非虚的registerTimer(夸平台)分配一个唯一的定时器ID,然后调用虚函数 registerTimer 一平台相关的方式来真正完成定时器的注册或创建。通过这种方法,平台的事件派发其维护着每个QObject对象关联的定时器的列表。 QAbstractEventDispatcher::registeredTimers 可被用来查询QObject对象所关联的定时器的列表。
在底层,一旦你注册一个定时器,它的时间间隔将不能被改变而且应当被看作不可变的。一个高层的API可以创建一个新的定时器来改变时间间隔。同时,定时器会持续发射(也就是循环)直到被杀死,而且在事件派发器接口中也没有 "单次发射(single shot)" 的概念。高层的API必须在第一次发射后通过反注册来模拟一个单次发射。
定时器 id 分配算法
定时器ID的分配算法是无锁的而且可从 Brad的博客 中学习。
一个重要的事实是,定时器ID必须是唯一的(甚至在跨线程的情况下)。这是因为当一个QObject从一个线程移动到另一个时,它的定时器也会随它一起移动(即信号和事件现在要通过新线程的事件循环进行传递)。移动定时器是一个简单从旧线程的派发器注销计时器并在在新线程的派发器中注册的问题。如果定时器的ID是不是唯一的,定时器ID可能会与新线程中现有的定时器发生冲突。如果新的定时器ID在QObject::moveToThread时被重新生成,那么应用程序需要被通知id信息发生更改,而且这对程序员来说是一个麻烦。
Unix (不用 glib)
在Unix中,QEventDispatcherUNIX实现了事件派发。它按照到期时间维护着一个计时器的列表。当计时器注册时,它的到期时间是通过在单调时钟(clock_gettime)上加上超时值来计算出来的。在不支持单调时钟的系统中函数gettimeofday会被使用。然后按照它的到期时间间隔定时器被插入排序后的列表中。
派发器使用 select() 来等待所有的 fds(X 连接,套接字)事件。当进入 processEvents() 时它首先为所有注册的计时器更新到期时间间隔。然后将定时器列表中第一项的时间间隔作为超时值提供给系统调用 seletc()。从 select 返回后,派发器通过给关联的 QObject 发射 QEvent::Timer 来激活到期的定时器。
注意,Qt 未使用 POSIX 定时器的API - timerfd_create(为什么?可移植性?)。
Unix (使用 glib)
Qt 可以使用 glib 事件循环(默认)来编译,它有助于与Gtk 更好地集成。
类似于 QEventDispatcherUNIX,QEventDispatcherGlib 管理一个按到期时间排序的定时器的列表。它为定时器创建新的 GSource。该自定义 GSource 的准备、检查与派发函数在定时器列表上以显而易见的方式的工作(fix me)。
Windows
Qt 创建一个带回调函数的隐藏窗口来处理事件。当定时器被注册时,QEventDispatcherWin 将基于定时器的时间间隔创建的 Windows原生的定时器。
- 如果时间间隔大于20毫秒,它使用 SetTimer(伴随着QAbstractEventDispatcher::registerTimer生成的id)。SetTimer发送WM_TIMER消息到回调函数中并作为QEvent::Timer发送到QObject。
- 如果时间间隔小于20毫秒,Qt将尝试通过timeSetEvent使用多媒体(也就是fast)定时器。timeSetEvent 接受一个回调函数,到期时它会被调用并在一个独立的线程中被调用。在Qt中,定时器在QObject相同的线程中发射。因此 timeSetEvent 的回调函数 post 一个消息到派发器,派发器将获得该消息并作为 QEvent::Timer 发送。
- 如果时间间隔为0,注册时派发器将post一个特殊的消息(QEvent::ZeroTimerEvent)到它自身。派发器通过发送QEvent::Timer到相应的QObject并post一个ZeroTimerEven到它自身来处理该事件。
API 实现
QObject::startTimer 使用事件派发器注册一个新的定时器。这大约等价于,QAbstractEventDispatcher::instance(object->thread())->registerTimer(interval, object).
QTimer 是一个 QObject。他所做的就是 QObject::startTimer()。它在自己的timerEvent()中发射 activated() 信号。
QBasicTimer
QTimer由于本身是一个QObject所以显得比较笨拙,而且Qt代码一般都尽量避免使用它。与此同时,使用 QObject::startTimer() API 又比较容易出错。
- 当停止定时器时,在QObject::killTimer(m_timerId)后你必须使定时器id变无效(比如说-1)。这是需要的,因为m_timerId的正值在你的代码中指示该定时器处于激活状态。
- 要重启一个定时器,你需要牢记如果m_timerId不为-1的话要杀死(kill)掉老的定时器。
QBasicTimer 解决了上面这个问题,它只是整数 "timer id" 的一个华而不实的接口。QBasicTimer::start(int msec, QObject *) 将杀死任何可能存在的定时器并使用 object->startTimer(msec) 创建一个新的。QBasicTimer::stop() 将杀死该定时器并使得id无效。QBasicTimer::timerId() 提供定时器 id。