异步实现事件的定时执行 - QTimer和QThread的联合使用
- 引言
- 一、核心源码
- 二、其信号和槽函数简述
- 三、定时器及其moveToThread简述
引言
在 Qt 中,如果想要定时执行某些事件或函数,通常会使用 QTimer 类。QTimer 允许设置一个时间间隔,当这个时间间隔过去后,它会发出一个信号。可以将这个信号连接到一个槽函数,从而在该时间间隔到达时执行特定的操作。如果想要实现定时的操作是异步执行 (不阻塞主线程),可通过
moveToThread将定时器移动到一个线程中,信号和槽的连接类型使用Qt::DirectConnection,保证槽函数执行是在定时器的线程中。效果如下图所示 (一秒执行一次):

一、核心源码
-  - 创建定时器以及线程,设定执行事件
 
    this->m_timer = new QTimer(nullptr);this->m_thread = new QThread(this);m_timer->setInterval(1000);m_timer->moveToThread(m_thread);connect(m_timer, &QTimer::timeout, this, [&](){QString time = QDateTime::currentDateTime().toString("hh:mm:ss.zzz");qDebug()<< time << " timer Thread ID:" << QThread::currentThreadId();}, Qt::DirectConnection);connect(m_thread, &QThread::started, m_timer, QOverload<>::of(&QTimer::start));connect(m_thread, &QThread::finished, m_timer, &QTimer::stop);m_thread->start();qDebug() << "Main Thread ID:" << QThread::currentThreadId();
首先创建定时器和线程,设定定时器间隔时间为1秒,并将定时器移动到线程中。方便起见直接使用兰姆达表达式设定定时器的定时槽函数. 使用信号和槽的形式调用定时器相关函数 (开始和停止)。最后启动线程即可.
-  - 内存释放
 
    m_thread->quit();m_thread->wait();m_thread->deleteLater();m_timer->deleteLater();
一般在父类或者父窗体的析构函数中执行,停止执行,释放内存。
二、其信号和槽函数简述
信号与槽相关知识可参考:
Qt 信号与槽的使用详解 - 多种绑定形式、同步异步、Lambda表达式等:https://blog.csdn.net/qq_38204686/article/details/139702275
-  - 使用信号和槽的形式调用定时器相关函数
 由于定时器已经被移动到线程中,所以不能直接在主线程中调用定时器相关函数。比如执行定时器停止m_timer->stop();会显示QObject::killTimer: Timers cannot be stopped from another thread。
 
- 使用信号和槽的形式调用定时器相关函数
可参考
Qt: QTimer和QThread:https://www.cnblogs.com/lingdhox/p/4218051.html
https://stackoverflow.com/questions/53200294/qthread-with-qtimer-connection-issues
-  - 定时器开始槽函数需使用QOverload<>::of重载
 &QTimer::start有多个重载函数,比如void QTimer::start(int msec)和void QTimer::start(),需使用QOverload<>::of指定调用哪一个重载函数 - (在<>中指明参数,比如<int>)。
 如果只这样写connect(m_thread, &QThread::started, m_timer, &QTimer::start);会报错,
 这样connect(m_thread, SIGNAL(started()), m_timer, SLOT(start()));是可行的,但不建议。
 
- 定时器开始槽函数需使用
可参考
QThread with QTimer connection issues:https://stackoverflow.com/questions/53200294/qthread-with-qtimer-connection-issues
QT-信号槽有多个重载版本{ QOverload<_>::of(&::) }:https://blog.csdn.net/ugetoneshot/article/details/139169027
三、定时器及其moveToThread简述
-  - 创建定时器new QTimer(nullptr)参数parent为空而不是this
 后续需要将定时器移动到另一线程,所以其父对象需为空
 
- 创建定时器
void QObject::moveToThread(QThread *targetThread)的官方解释:
更改此对象及其子对象的线程相关性。如果对象有父对象,则无法移动该对象,事件处理将在targetThread中继续。使用时需注意:此函数只能将对象从当前线程“推”到另一个线程,而不能将对象从任意线程“拉”到当前线程。
-  - 关于定时器的精度问题.
 如下方左图所示,近似每秒一触发,但是误差在2-3毫秒,设置setTimerType(Qt::PreciseTimer);之后误差只有1毫秒。
 
- 关于定时器的精度问题.
 
 
可参考
QT使用高精度定时器:https://blog.csdn.net/ljjjjjjjjjjj/article/details/130189550