[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事件循环机制中加入不耗时的事件操作等来实现的。

相关推荐
三月微暖寻春笋44 分钟前
【和春笋一起学C++】(四十九)C++中string类的简介
c++·cstring·string类·string类的实现·string类方法
Bona Sun1 小时前
单片机手搓掌上游戏机(二十一)—pico运行doom之修改编译
c语言·c++·单片机·游戏机
松涛和鸣1 小时前
23、链式栈(LinkStack)的实现与多场景应用
linux·c语言·c++·嵌入式硬件·ubuntu
水天需0101 小时前
Linux 命令查找名为 main.cpp 文件
qt
CC.GG1 小时前
【C++】面向对象三大特性之一——继承
java·数据库·c++
Tandy12356_1 小时前
手写TCP/IP协议栈——数据包结构定义
c语言·网络·c++·计算机网络
繁华似锦respect1 小时前
HTTPS 中 TLS 协议详细过程 + 数字证书/签名深度解析
开发语言·c++·网络协议·http·单例模式·设计模式·https
minji...2 小时前
linux 进程控制(一) (fork进程创建,exit进程终止)
linux·运维·服务器·c++·git·算法
埃伊蟹黄面2 小时前
双指针算法
数据结构·c++·算法