跳转到内容
新建笔记

Qt 定时器:事件循环、周期任务与单次延迟

QTimer 在所属线程的事件循环中安排后续工作。它适合定期刷新界面、轮询轻量状态和延迟执行一次操作。它不创建后台线程,也不保证每个回调都在指定时刻准时运行。

1. 一次 timeout 如何发生

跳转到“1. 一次 timeout 如何发生”
sequenceDiagram
participant Code as 应用代码
participant Timer as QTimer
participant EventLoop as 线程事件循环
participant Slot as 槽或回调
Code->>Timer: 设置间隔并 start()
Timer->>EventLoop: 注册计时请求
Note over EventLoop: 到期且事件循环可以处理事件
EventLoop->>Timer: 处理计时事件
Timer->>Slot: 发出 timeout()
Slot-->>EventLoop: 尽快返回

若线程还没有进入 exec(),或者被耗时循环阻塞,计时请求不能按预期得到处理。Qt 根据对象的线程归属处理定时器;启动和停止也必须在该定时器所属线程完成。依据:Qt 的定时器机制。

下面是一个 Qt 6 Core 控制台程序,无需窗口或 Q_OBJECT 宏。它请求每 100 ms 输出一次,第三次输出后结束。

#include <QCoreApplication>
#include <QDebug>
#include <QElapsedTimer>
#include <QTimer>
int main(int argc, char *argv[]) {
QCoreApplication app(argc, argv);
QTimer timer;
QElapsedTimer elapsed;
int count = 0;
timer.setInterval(100);
timer.setTimerType(Qt::PreciseTimer);
QObject::connect(&timer, &QTimer::timeout, &app, [&] {
++count;
qInfo() << "tick" << count << "elapsed(ms)" << elapsed.elapsed();
if (count == 3) {
timer.stop();
app.quit();
}
});
elapsed.start();
timer.start();
return app.exec();
}

保存为 main.cpp,最小构建文件:

cmake_minimum_required(VERSION 3.21)
project(TimerDemo LANGUAGES CXX)
set(CMAKE_CXX_STANDARD 17)
find_package(Qt6 REQUIRED COMPONENTS Core)
add_executable(timer-demo main.cpp)
target_link_libraries(timer-demo PRIVATE Qt6::Core)

观察输出应有 tick 1、tick 2、tick 3。实际经过时间可能大于 100、200、300 ms,不能把这些数值当成严格验收阈值。这个例子用局部变量保存定时器,因为 app.exec() 返回之前作用域不会结束。

3. 单次执行与生命周期

跳转到“3. 单次执行与生命周期”

在已经创建 app 的程序中安排一次回调:

QTimer::singleShot(250, &app, [] {
qInfo() << "Run once after the delay";
});

带 context 的重载使回调与上下文对象关联:上下文销毁后不会再调用该回调。若 lambda 捕获其他裸指针,仍要另外保证它们的有效期。QTimer 文档说明了单次调用、线程与超时行为。

需要反复取消或重新安排时,保留一个 QTimer 对象,并设置 setSingleShot(true),用 start() 重置等待、用 stop() 取消。

QTimer debounce;
debounce.setSingleShot(true);
debounce.setInterval(300);
QObject::connect(&debounce, &QTimer::timeout, &app, [] {
qInfo() << "Perform a lightweight save request";
});
// 每次输入变化时调用 debounce.start();连续变化会重新开始等待。

上例只是核心片段。实际界面中让 debounce 成为长期存活的成员,或者使用 new QTimer(this) 交给父对象管理,不能把它创建在立即返回的普通函数里再期待它继续触发。

4. 精度、延迟与计时用途

跳转到“4. 精度、延迟与计时用途”

Qt::PreciseTimer 尝试提高精度并避免提前触发,系统忙时仍可晚到。发生多次到期积压时,Qt 不会机械补发同样数量的 timeout()。因此:

  • 刷新界面可以用定时器,但计算真实经过时间应读取单调计时器,如 QElapsedTimer。
  • 不要用“触发次数 × 设定间隔”作为准确时长。
  • 采样时钟、运动控制脉冲等硬实时任务应由相应硬件或实时机制实现。
  • 耗时 I/O 和计算应采用异步接口或工作线程;回调里的长任务同样会卡住事件循环。

QTimer 的整数间隔单位为毫秒,范围约 24 天。Qt 6.8 起还提供范围更大的 QChronoTimer;其更细的时间表示也不等于操作系统具有同样的实际调度精度。版本与范围参见 Qt Timers。

  1. timer.isActive() 是否为真,间隔是否为合理的正数?
  2. 对象是否仍存活,connect 是否连接到预期对象?
  3. 所属线程是否运行事件循环,是否被阻塞?
  4. 是否从错误线程调用了 start() 或 stop()?
  5. 是否把一次迟到理解成定时器停止,或者误把它当成独立后台线程?

原笔记只留下“定时器”标题。本页补足了可运行示例、生命周期和时序边界,作为后续 Qt 周期操作的统一入口。