一、timerEvent
timerEvent 处理多个定时任务的核心逻辑是:通过「定时器 ID」区分不同任务,每个定时器通过 startTimer(interval) 启动后,会返回一个唯一的「定时器 ID」;timerEvent 触发时,通过 QTimerEvent::timerId() 获取当前触发的 ID,分支判断后执行对应任务。
#ifndef WIDGET_H
#define WIDGET_H
#include <QWidget>
QT_BEGIN_NAMESPACE
namespace Ui {
class Widget;
}
QT_END_NAMESPACE
class Widget : public QWidget
{
Q_OBJECT
public:
Widget(QWidget *parent = nullptr);
~Widget();
void timerEvent(QTimerEvent *e);//重写定时器事件
private:
Ui::Widget *ui;
};
#endif // WIDGET_H
#include "widget.h"
#include "ui_widget.h"
#include <QDebug>
#include <QTimer>
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
/*定时器方式一*/
//启动定时器
startTimer(1000);
//定时器的返回值timerId是int类型,可以根据timerId来区分timerEvent汇总执行哪些代码
//killTimer(timerId);//传入定时器id,清除对应定时器
}
/*定时器方式一*/
void Widget::timerEvent(QTimerEvent *e)
{
//int timerId = e.timerId();
//if(timerId == ){}; //根据timerId判断是哪个定时器,进行对应操作
static int num = 1;
ui->label->setText(QString::number(num++));
}
Widget::~Widget()
{
delete ui;
}
动态管理定时器
class DynamicTimerWidget : public QWidget {
Q_OBJECT
public:
DynamicTimerWidget(QWidget *parent = nullptr) : QWidget(parent) {
// 初始化时添加 2 个基础定时器
addTimer(1000, "界面刷新"); // 1秒
addTimer(2000, "数据上传"); // 2秒
}
private slots:
// 动态添加定时器(如按钮点击触发)
void on_addBtn_clicked() {
int interval = ui->intervalSpin->value(); // 从SpinBox获取用户输入的周期(毫秒)
QString taskName = ui->taskLineEdit->text(); // 任务名称
addTimer(interval, taskName);
qDebug() << "添加定时器:" << taskName << ",周期:" << interval << "ms";
}
// 动态移除指定定时器(如按钮点击触发)
void on_removeBtn_clicked() {
QString taskName = ui->taskLineEdit->text();
removeTimer(taskName);
}
protected:
void timerEvent(QTimerEvent *e) override {
int currentTimerId = e->timerId();
// 通过定时器 ID 查找对应的任务名称
if (timerMap.contains(currentTimerId)) {
QString taskName = timerMap[currentTimerId];
// 执行对应任务
executeTask(taskName);
}
}
private:
QMap<int, QString> timerMap; // 关键:定时器 ID → 任务名称
// 封装:添加定时器
void addTimer(int interval, const QString &taskName) {
int timerId = startTimer(interval);
timerMap.insert(timerId, taskName); // 保存 ID 和任务名称的映射
}
// 封装:移除定时器
void removeTimer(const QString &taskName) {
// 遍历找到任务对应的定时器 ID
for (auto it = timerMap.begin(); it != timerMap.end(); ++it) {
if (it.value() == taskName) {
killTimer(it.key()); // 停止定时器
timerMap.erase(it); // 从映射中删除
qDebug() << "移除定时器:" << taskName;
return;
}
}
qDebug() << "未找到定时器:" << taskName;
}
// 执行任务逻辑
void executeTask(const QString &taskName) {
if (taskName == "界面刷新") {
ui->timeLabel->setText(QDateTime::currentDateTime().toString("hh:mm:ss.zzz"));
} else if (taskName == "数据上传") {
qDebug() << "任务:数据上传,时间:" << QDateTime::currentDateTime().toString();
} else {
// 自定义任务(用户动态添加的任务)
qDebug() << "任务:" << taskName << ",时间:" << QDateTime::currentDateTime().toString();
}
}
};
注意事项
- 定时器 ID 的生命周期
startTimer() 返回的 ID 是唯一的,直到调用 killTimer(id) 或对象销毁,ID 才会失效;
若重复调用 startTimer(),会生成新的 ID,原定时器仍在运行(需手动停止原 ID 对应的定时器,避免重复触发)。 - 避免阻塞定时器
timerEvent 运行在对象所属的线程(默认主线程),若任务逻辑耗时过长(如超过定时器周期),会导致定时器触发不及时;
解决:耗时任务(如文件读写、网络请求)放在子线程执行,定时器仅负责触发任务,不直接执行耗时操作。 - 线程安全问题
定时器的 timerEvent 始终在「启动定时器的线程」中执行,若线程是子线程,禁止直接操作 UI 控件(Qt UI 操作需在主线程);
解决:子线程定时器触发后,通过 signal-slot(跨线程连接)通知主线程更新 UI。 - 停止定时器的正确方式
必须传入正确的定时器 ID 调用 killTimer(id),否则可能停止错误的定时器;
对象销毁前,建议手动停止所有定时器(避免野指针或无效触发) - 定时器精度
Qt 的 startTimer() 是基于事件循环的定时器,精度受事件循环繁忙程度影响(毫秒级精度,适合常规场景);
若需高精度定时器(如微秒级),建议使用 QTimer 配合 Qt::PreciseTimer 类型,或直接使用系统级定时器 API。
二、QTimer
QTimer 是 Qt 提供的高层定时类,封装了底层定时器逻辑,通过 timeout() 信号触发定时任务,无需重写事件处理器(如 timerEvent);
核心优势:支持单次触发 / 重复触发、动态修改周期、暂停 / 重启、高精度模式,且线程安全(需配合信号槽跨线程连接);
依赖 Qt 事件循环:QTimer 的触发依赖 QApplication::exec() 启动的事件循环,若事件循环被阻塞(如耗时操作),定时器会延迟触发。
#include "widget.h"
#include "ui_widget.h"
#include <QDebug>
#include <QTimer>
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
/*定时器方式二*/
static int num = 1;
//创建 QTimer 对象(父对象设为 this,自动管理生命周期,避免内存泄漏)
QTimer * timer = new QTimer(this);
//timer->setInterval(1000); // 周期:1000ms = 1秒
//timer->setSingleShot(false); // 重复触发(默认 false,设为 true 则单次触发)
connect(timer,&QTimer::timeout,this,[=](){
ui->label->setText(QString::number(num++));
});
//启动定时器
timer->start(20);
}
Widget::~Widget()
{
delete ui;
}
关键API速查表
| 函数/信号 | 功能描述 |
|---|---|
| setInterval(int ms) | 设置定时周期(单位:毫秒) |
| setSingleShot(bool) | 设置是否单次触发(true:触发一次后自动停止) |
| start() | 启动定时器(若已启动,会重启) |
| stop() | 停止定时器(可通过 start() 重启) |
| isActive() | 判断定时器是否正在运行(返回 bool) |
| timeout() | 信号 定时周期到触发的信号(核心信号) |
| setTimerType(Qt::TimerType) | 设置定时器精度(如 Qt::PreciseTimer 高精度) |
常见问题
-
定时器不触发?
原因 1:未启动事件循环(如 main 函数中未调用 QApplication::exec());
原因 2:定时器未调用 start(),或被 stop() 停止后未重启;
原因 3:QTimer 对象被提前销毁(如未设置父对象,或手动 delete 了);
原因 4:事件循环被阻塞(如主线程执行耗时操作,未让出 CPU 时间);
解决:确保事件循环启动、定时器已 start()、对象生命周期正确、耗时操作放在子线程。 -
定时器触发延迟?
原因 1:事件循环繁忙(主线程有大量 UI 操作或耗时任务);
原因 2:定时器周期过短(如 1ms),超出系统处理能力;
原因 3:使用了普通精度定时器(Qt::CoarseTimer);
解决:优化主线程逻辑、延长周期、改用高精度定时器,或用子线程定时器。 -
线程安全问题(子线程操作 UI)
错误示例:子线程中 QTimer 的槽函数直接修改 ui->label->setText();
后果:程序崩溃或界面错乱(Qt UI 操作必须在主线程);
解决:通过信号槽跨线程连接(默认 Qt::QueuedConnection):

被折叠的 条评论
为什么被折叠?



