1、理解同步与异步定时器
同步定时器:QTimer的timeout处理函数运行在创建QTimer的线程上,哪怕你创建了N多个QTimer,他们都是跑在同一个线程上的,假设其中一个QTimer处理耗时操作,那么其他QTimer被阻塞,所以QTimer属于同步定时器。
异步定时器:定时器的timeout处理函数不是运行在创建它的线程上,运行在新线程上,定时器依附于此线程,当你建立多个定时器时,每个定时器依附于不同的线程上,所以不会因为其中一个定时器阻塞,导致其他定时器无法执行,因为他们位于不同的线程上,可以异步执行。
异步定时器的用途:需要以多线程方式执行一些低频次的耗时操作,例如调用某个接口,获取数据,并更新到界面,而调用的接口又比较消耗cpu时间,所以同步定时器无法胜任。
2、自己动手实现异步定时器
首先,我们创建QTimer和线程,并moveToThread到此线程,通过信号槽将QTimer的启动和删除分别绑定到线程的启动和结束时刻,并连接QTimer槽函数,注意此处一定是Qt::DirectConnection连接方式,否则槽函数可能不会在新线程上运行。
#include "AsyncTimer.h"
#include <QTimer>
/**
* @brief AsyncTimer::AsyncTimer
* 建立线程,定时器
* @param msec 超时时间
* @param parent 父节点
*/
AsyncTimer::AsyncTimer(int msec, QObject *parent)
:QObject(parent)
{
timer = new QTimer();
timer->setInterval(msec);
timer->moveToThread(&thread);
connect(timer, SIGNAL(timeout()), this, SLOT(run()), Qt::DirectConnection);
connect(&thread, SIGNAL(finished()), timer, SLOT(deleteLater()));
connect(&thread, SIGNAL(started()), timer, SLOT(start()));
thread.start();
}
/**
* @brief AsyncTimer::~AsyncTimer
* 退出线程,释放定时器
*/
AsyncTimer::~AsyncTimer()
{
waitForQuit();
}
/**
* @brief AsyncTimer::waitForQuit
* 等待线程退出函数,在子类析构函数中调用(推荐)。
* 避免线程还在运行时,子类就开始析构,若此时线程中还在使用被析构的子类成员,则发生段错误。
*/
void AsyncTimer::waitForQuit()
{
thread.quit();
thread.wait();
}
#ifndef ASYNCTIMER_H
#define ASYNCTIMER_H
#include <QThread>
class QTimer;
/**
* @brief The AsyncTimer class
* 异步定时器:
* 创建一个新线程并在此线程上执行定时器,
* 定时器周期性调用子类run()方法,执行在新线程上。
*/
class AsyncTimer : public QObject
{
Q_OBJECT
public:
AsyncTimer(int msec=1000, QObject* parent=nullptr);
virtual ~AsyncTimer();
protected:
void waitForQuit();
protected slots:
virtual void run() = 0;
private:
QThread thread;
QTimer* timer;
};
#endif // ASYNCTIMER_H
3、测试
TestTimer继承自AsyncTimer,实现run()。测试run()是否跑在新线程上,并跨线程返回执行结果。
但是此处TestTimer::connect的连接方式,使用lambda,决定了:
qDebug() << "task result : " << result << " in thread : " << QThread::currentThreadId();
这句话执行在run()所在线程。如果用queue方式,那么此句话应该不是执行在run()所在线程。
#include "TestTimer.h"
#include <QDebug>
TestTimer::TestTimer(QObject *parent)
:AsyncTimer(1000, parent)
{
}
TestTimer::~TestTimer()
{
// 注意:若在此类run()中使用了成员变量,则在此类对象析构前,
// 必须调用waitForQuit()让线程先退出,否则可能出现此类成员变量已被析构,
// 但是线程仍然还在运行run()的情况,此时访问成员变量出现段错误。
// 建议:默认在子类析构函数中调用waitForQuit()。
waitForQuit();
}
void TestTimer::run()
{
// to do ...
qDebug() << "do task in thread : " << QThread::currentThreadId();
QThread::msleep(1000); // 模拟do somthing
emit statusChanged(55); // 处理完毕,返回结果
}
#ifndef TESTTIMER_H
#define TESTTIMER_H
#include "AsyncTimer.h"
class TestTimer : public AsyncTimer
{
Q_OBJECT
public:
TestTimer(QObject* parent=nullptr);
virtual ~TestTimer();
protected slots:
virtual void run();
signals:
void statusChanged(int result);
};
#endif // TESTTIMER_H
#include <QCoreApplication>
#include <QDebug>
#include "TestTimer.h"
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
qDebug() << "main thread : " << QThread::currentThreadId();
TestTimer timer;
TestTimer::connect(&timer, &TestTimer::statusChanged, [=] (int result) {
qDebug() << "task result : " << result << " in thread : " << QThread::currentThreadId();
});
return a.exec();
}
若对你有帮助,欢迎点赞、收藏、评论,你的支持就是我的最大动力!!!
同时,阿超为大家准备了丰富的学习资料,欢迎关注公众号“超哥学编程”,即可领取。
本文涉及工程代码,公众号回复:31AsyncTimer,即可下载。