异步实现事件的定时执行 - 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