[C++] QTimer与Qt事件循环机制 实验探究

一、前言

之前在学Qt的的时候,老是搞不清楚Qt什么时候做耗时操作会导致UI阻塞什么时候又不会。本文通过记录学Qt时的一个小实验来探索Qt背后的核心机制---事件循环。

本文代码仓库为ricardo/qt槽函数线程实验,具体代码版本为ricardo/qt槽函数线程实验 - Gitee.com,如果我的代码对您学习Qt、完成课程作业等有些许帮助的话,还请对该仓库点一个star,祝您生活愉快。

如果本文被CSDN自动设为VIP可见,请从我的CSDN个人简介去到我的Gitee主页寻找代码。

二、实验设置

本实验大致代码与前文[C++ ]qt槽函数及其线程机制-CSDN博客的代码大致上一样,但有新的改变。

上文中主要是在两个槽函数中向一个多线程同步队列中分别push奇数偶数元素,来探索qt槽函数的线程机制。而本文在此基础上,将上文中两个槽函数的内容(耗时操作)放到多线程里面去实现,同时通过一个进度条来实时显示队列中占用空间的百分比。

2.1 定时刷新主界面ui实现思路

同步队列是在主界面类中定义的,由于其size设置得比较大(2000000),原本槽函数中就是会压入2000000个元素,所以每push一个元素就更新一次ui界面肯定是不太合理的。由于人眼对实时性的要求是24HZ,所以只要保证界面刷新速率在24HZ及以上就OK,完全不需要去"真正的实时"根据每时每刻的变化来刷新界面。

到这里设计思路就很明显啦,只需要一个计时器每隔42ms触发一个ui刷新的信号或者函数就可以啦。

cpp 复制代码
// .h中MainWindow界面类中定义
	QTimer* timer;

// .cpp中MainWindow界面类中初始化
    // 精确设置24Hz:1000ms / 24 ≈ 42ms
    // 使用Qt::PreciseTimer提高精度
    timer = new QTimer(this);
    timer->setTimerType(Qt::PreciseTimer);
    timer->start(42);  // 42ms间隔

利用上面的函数就可以实现定时触发计时器信号即可完成ui定时刷新。

cpp 复制代码
connect(timer, &QTimer::timeout,this, &MainWindow::onRefreshTimerTimeout);

2.2 多线程中运行耗时工作

在上一篇文章的实验中,主线程的槽函数中运行这非常耗时的操作,这会导致ui界面非常卡顿,为此做出的改进思路如下:

cpp 复制代码
//定义一个Worker类继承QObject,内含一个function对象,接收不同的耗时函数操作

//在MainWindow中定义若干Worker类对象和QThread对象,使用moveToThread,将worker对象所处线程移入到QThread线程中。
    worker1 = new Worker();
    worker2 = new Worker();
    worker3 = new Worker();
    thread1 = new QThread(this);
    thread2 = new QThread(this);
    thread3 = new QThread(this);
    worker1->moveToThread(thread1);
    worker2->moveToThread(thread2);
    worker3->moveToThread(thread3);

    worker1->setFunc([&](){
        qDebug() << "开始压入数据";
        for(int i = 0; i < 200000; i += 2){
            sq.push(i);
            qDebug() << i;
//            if(timer1.elapsed() > 100){
//                ui->progressBar->setValue(static_cast<int>(sq.length()*100/capacity));
//                timer1.restart();
//            }
        }
    });
    worker2->setFunc([&](){
        for(int i = 1; i < 200000; i += 2){
            sq.push(i);
            qDebug() << i;
//            if(timer2.elapsed() > 100){
//                ui->progressBar->setValue(static_cast<int>(sq.length()*100/capacity));
//                timer2.restart();
//            }
        }
    });

    worker3->setFunc([&](){
//        sq.clear(); 该代码会通过一个lock_guard锁住然后进行长时间的while循环 pop元素,会导致ui卡主
        //利用下面的代码替代上面的,避免ui卡主
        while(!sq.empty()){
            sq.try_pop();
        }
    });
    thread1->start();
    thread2->start();
    thread3->start();

这种方式是QT官方比较推荐的多线程编程方式。

三、原理探究---事件循环

根据前文中QT ui主线程机制的探索,本文的QTimer是在主界面类的主线程运行的,设置的是42ms提醒一次,在Qt主线程里面使用QTimer这种计时的操作,会不会导致阻塞。

答案是不会!

QTimer实际工作原理如下:

  1. timer->start() 只是向事件循环注册一个定时器事件
  2. 主线程继续执行,不会停在这里
  3. Qt底层会管理定时器
  4. 每过42ms,Qt向事件队列投递一个QTimerEvent
  5. 主线程空闲时处理这个事件,触发timeout()信号

而主线程事件循环机制可以用如下伪代码表示:

cpp 复制代码
// Qt主线程的核心循环(简化版)
while (app.isRunning()) {
    // 1. 处理所有待处理的事件(鼠标、键盘、定时器、网络等)
    while (event = getNextEvent()) {
        processEvent(event);  // 调用你的onRefreshTimerTimeout()
    }
    
    // 2. 如果没有事件,等待(不占用CPU)
    waitForEvents();  // 进入休眠,直到有新事件
    
    // 3. 重复
}

在一个时间线上,有可能是这样发生的

时间(t+0ms): start() 调用,立即返回

时间(t+10ms): 用户点击按钮,处理点击事件

时间(t+20ms): 处理网络数据

时间(t+42ms): Qt投递定时器事件

时间(t+45ms): 事件循环空闲,处理timeout信号 → 调用你的槽函数

时间(t+50ms): 继续处理其他事件

这里的定时器事件就与主线程UI,更新画面的绘制事件一样,都是事件,通过事件循环机制来在主线程中轮询处理。

当然在上述的时间线中同样有导致ui卡顿的可能。可以这样理解,把上述时间线看成若干可变长的时间片,每一个时间片会去完成一个"操作",比如" 用户点击按钮,处理点击事件","Qt投递定时器事件"," 事件循环空闲,处理timeout信号 → 调用你的槽函数"这三个操作就对应三个时间片。

在" 事件循环空闲,处理timeout信号 → 调用你的槽函数"中,如果调用的槽函数非常耗时,那么这个时间片就会无限增长知道该操作处理完成。而UI画面更新操作update()也是在事件循环里面的,假如其前面就有很长的时间片在处理其他操作,就会导致update()不能及时执行。这也是为什么Qt的主线程不能执行耗时操作的原因。

一句话总结:Qt的事件循环机制运行在主线程中,很多像QTimer这种异步操作都是在Qt事件循环机制中加入不耗时的事件操作等来实现的。

相关推荐
王老师青少年编程13 小时前
csp信奥赛C++高频考点专项训练之贪心算法 --【哈夫曼贪心】:合并果子
c++·算法·贪心·csp·信奥赛·哈夫曼贪心·合并果子
叼烟扛炮13 小时前
C++第二讲:类和对象(上)
数据结构·c++·算法·类和对象·struct·实例化
样例过了就是过了15 小时前
LeetCode热题100 最长公共子序列
c++·算法·leetcode·动态规划
谭欣辰15 小时前
C++ 排列组合完整指南
开发语言·c++·算法
橙子也要努力变强16 小时前
信号捕捉底层机制-机理篇2
linux·服务器·c++
盐焗鹌鹑蛋16 小时前
【C++】stack和queue类
c++
小短腿的代码世界17 小时前
Qt实时盈亏计算深度解析:从持仓数据到动态盈亏展示
开发语言·qt
郝学胜-神的一滴17 小时前
罗德里格斯旋转公式(Rodrigues‘ Rotation Formula)完整推导
c++·unity·godot·图形渲染·three.js·unreal
lzh2004091917 小时前
深入理解进程:从PCB内核结构到写时拷贝的底层实战
linux·c++
aseity18 小时前
跨平台项目中QString 与 非Qt 跨平台动态库在字符集上的一个实用的互操作约定.
c++·经验分享