Qt实现异步定时器

1、理解同步与异步定时器

同步定时器:QTimer的timeout处理函数运行在创建QTimer的线程上,哪怕你创建了N多个QTimer,他们都是跑在同一个线程上的,假设其中一个QTimer处理耗时操作,那么其他QTimer被阻塞,所以QTimer属于同步定时器。

异步定时器:定时器的timeout处理函数不是运行在创建它的线程上,运行在新线程上,定时器依附于此线程,当你建立多个定时器时,每个定时器依附于不同的线程上,所以不会因为其中一个定时器阻塞,导致其他定时器无法执行,因为他们位于不同的线程上,可以异步执行。

异步定时器的用途:需要以多线程方式执行一些低频次耗时操作,例如调用某个接口,获取数据,并更新到界面,而调用的接口又比较消耗cpu时间,所以同步定时器无法胜任。

2、自己动手实现异步定时器

首先,我们创建QTimer和线程,并moveToThread到此线程,通过信号槽将QTimer的启动和删除分别绑定到线程的启动和结束时刻,并连接QTimer槽函数,注意此处一定是Qt::DirectConnection连接方式,否则槽函数可能不会在新线程上运行。

AsyncTimer.cpp:

#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();
}

AsyncTimer.h:

#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()所在线程。

TestTimer.cpp

#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);  // 处理完毕,返回结果
}

TestTimer.h

#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

main.cpp

#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,即可下载

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

百里杨

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值