• QT延时/等待


    一、阻塞型延时

    阻塞的原理就是:在延迟期间,本线程的事件循环得不到处理

    1.QThread类的sleep()

    最简单的延时方法就是使用QThread类的sleep(n)、msleep(n)、usleep(n),这几个函数的不良后果就是,GUI会在延时的时间段内失去响应,界面卡死,所以这三个函数一般用在非GUI线程中。

    QThread::msleep(50);//阻塞延时50ms
    
    • 1

    2.使用计时器,死等

    void Delay_MSec_Suspend(unsigned int msec)
    {    
        QTime _Time = QTime::currentTime().addMSecs(msec);
        while( QTime::currentTime() < _Time);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    二、非阻塞型延时

    原理无非就是利用事件循环,有两种原理:

    1.处理本线程的事件循环

    在等待中,不断强制刷新当前线程的事件循环,这样可以把事件队列中被阻塞的时间都处理掉,从而避免当前线程卡住(在主线程中,就是避免程序卡死)

    void Delay_MSec(unsigned int msec)
    {
        QTime _Time = QTime::currentTime().addMSecs(msec);
        while (QTime::currentTime() < _Time)
        {
            QCoreApplication::processEvents(QEventLoop::AllEvents, 100);
       	}
     }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    QCoreApplication::processEvents(QEventLoop::AllEvents, 100);这条语句能够使程序在while等待期间,去处理一次本线程的事件循环,处理事件循环最多100ms必须返回本语句,如果提前处理完毕,则立即返回这条语句。这也就导致了该Delay_MSec函数的定时误差可能高达100ms。

    2.使用子事件循环

    创建子事件循环,在子事件循环中,父事件循环仍然是可以执行的

    void Delay_MSec(unsigned int msec)
    {
        QEventLoop loop;//定义一个新的事件循环
        QTimer::singleShot(msec, &loop, SLOT(quit()));//创建单次定时器,槽函数为事件循环的退出函数
        loop.exec();//事件循环开始执行,程序会卡在这里,直到定时时间到,本循环被退出
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    3.耗时代码的处理

    假设有这样的应用情景:点击某个button之后,需要读入并处理一幅图像,需要耗时20秒才能处理完。

    在这20s内,GUI会失去效应,界面上的任何元素都无法被点击,这种情况应该怎么办?
    方法有两种:
    1、用另一个线程去处理这个耗时任务;
    2、在耗时任务中,不断地去处理本线程的事件循环,以保证GUI的及时响应;
    这里重点说一下第2种,参考 Qt线程与事件循环

    for(i=0; i < 1000000; i++)
    {
        //QCoreApplication::processEvents(QEventLoop::AllEvents);    //去处理本线程的事件循环,避免本线程被堵塞
        QCoreApplication::processEvents(QEventLoop::AllEvents, 5);//如果不够频繁,可以增加第二参数来缓解卡顿
     
        for(j=0; j < 1000000; j++)
        {
            //QCoreApplication::processEvents(QEventLoop::AllEvents);//处理事件循环,不建议放在这里,可能过于频繁
            doSomeThing();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    一般来说,processEvents()不宜被调用的过于频繁,也不宜被调用的不够频繁。过于频繁的话,一方面会使线程的响应更好,但另一方面会导致原本就耗时的任务变得更加耗时;不够频繁的话,显然可能会使GUI线程的响应变差,例如每500ms才被调用一次,那么GUI的事件循环就只能500ms才被处理一次,当然,这个问题可以通过设定processEvents()的第二个形参略微得到缓解,更好的做法是,保证被调的周期<200ms(再小一些更好,看程序需求),这样不至于肉眼可见的卡顿。

    副作用:(特别注意!)
    1、在点击按钮之后,这个20s的耗时任务开始执行,尚未执行完毕时,我们点击了GUI的关闭按钮,那么GUI会立即消失,但是这个耗时任务仍然会在后台执行,直到执行完毕,进程才会退出。解决办法:重写关闭事件,在关闭事件的函数中直接结束进程。

    2、在点击按钮之后,这个20s的耗时任务开始执行,执行到第5秒时,我们再次点击了这个按钮,那么QT又会执行一个新的20s任务,这个新任务完成后,又会接着把第一个20s任务从上次被打断的第5秒继续执行。如果这个任务是可重入的,后果仅仅是被执行了两遍,如果任务不可重入,那情况就彻底糟糕了。解决办法:点击按钮后把这个按钮disable掉,执行完再enable


    在嵌入式开发中,经常会遇到这样一种场景,需要轮询获取某个设备的状态
    例如:某个场景需要不断获取 激光测距模块物理设备 的测距值,但是激光测距模块此时不一定可以获取到正常的值,就需要轮询,不断的获取,直到获取到一次正常的值之后,打破轮询;或是达到超时时间打破轮询

    1.使用QTimer进行定时检查

    #include 
    #include 
    #include 
    
    class LaserDistanceSensor : public QObject
    {
        Q_OBJECT
    public:
        LaserDistanceSensor(QObject* parent = nullptr)
            : QObject(parent)
        {
            m_pollTimer = new QTimer(this);
            m_pollTimer->setInterval(200); //每200ms检查一次,距离值是否有效
            connect(m_pollTimer, &QTimer::timeout, this, &LaserDistanceSensor::checkDistance);
        }
    
        //开始轮询
        void startPolling()
        {
            if (!m_pollTimer->isActive())
            {
                m_pollTimer->start();
                QTimer::singleShot(5000, this, &LaserDistanceSensor::pollingTimeout); // 设置5秒超时,打破轮询
            }
        }
    
        // 模拟获取激光测距模块的值的函数
        double getDistanceValue()
        {
            return (rand() % 10 > 5) ? 1.0 : -1.0;
        }
    
        //检查是否已经获取到有效激光测距值
        void checkDistance()
        {
            double distance = getDistanceValue();
            if (distance > 0)
            {
                qDebug() << "有效的激光测距值:" << distance;
                m_pollTimer->stop(); // 停止轮询
            }
        }
    
        //轮询超时,未获取到有效值
        void pollingTimeout()
        {
            if (m_pollTimer->isActive())
            {
                qDebug() << "超时未获取有效激光测距值";
                m_pollTimer->stop();
            }
        }
    
    private:
        QTimer* m_pollTimer = nullptr;
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56

    外部调用 startPolling()函数就可以开始轮询了,达到条件就会打破轮询,请注意,如果startPolling()函数被调用的线程与LaserDistanceSensor 对象创建的线程不一致,QTimer就无法运行,因为QTimer的运行依赖事件循环,且只能在QTimer对象创建的线程中的事件循环中运行;

    非同一线程时,Qt报错:

    QObject::startTimer: Timers cannot be started from another thread
    QObject::startTimer: Timers can only be used with threads started with QThread
    
    • 1
    • 2

    这种情况时,我们可以使用信号槽的方式解决此问题,比如添加一个信号,在槽函数中调用startPolling(),就可以让QTimer顺利运行,信号可以在不具有事件循环的线程中发送,只要接收对象所处的线程有事件循环即可,发信号和QApplication::postEvent的作用机制类似,都是朝receiver的事件队列中放入一个事件/信号,等待receiver对象的事件循环来处理该事件/信号

    class LaserDistanceSensor : public QObject
    {
        Q_OBJECT
    public:
        LaserDistanceSensor(QObject* parent = nullptr)
            : QObject(parent)
        {
            m_pollTimer = new QTimer(this);
            m_pollTimer->setInterval(200); //每200ms检查一次,距离值是否有效
            connect(m_pollTimer, &QTimer::timeout, this, &LaserDistanceSensor::checkDistance);
    
            connect(this, &LaserDistanceSensor::sglStartPolling, this, &LaserDistanceSensor::startPolling);
        }
    
        void startPollingInOtherThread()
        {
            emit sglStartPolling();
        }
    
        //开始轮询
        void startPolling()
        {
            if (!m_pollTimer->isActive())
            {
                m_pollTimer->start();
                QTimer::singleShot(5000, this, &LaserDistanceSensor::pollingTimeout); // 设置5秒超时,打破轮询
            }
        }
    
        // 模拟获取激光测距模块的值的函数
        double getDistanceValue()
        {
            return (rand() % 10 > 5) ? 1.0 : -1.0;
        }
    
        //检查是否已经获取到有效激光测距值
        void checkDistance()
        {
            double distance = getDistanceValue();
            if (distance > 0)
            {
                qDebug() << "有效的激光测距值:" << distance;
                m_pollTimer->stop(); // 停止轮询
            }
        }
    
        //轮询超时,未获取到有效值
        void pollingTimeout()
        {
            if (m_pollTimer->isActive())
            {
                qDebug() << "超时未获取有效激光测距值";
                m_pollTimer->stop();
            }
        }
    
    signals:
        void sglStartPolling();
    
    private:
        QTimer* m_pollTimer = nullptr;
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62

    外部调startPollingInOtherThread()函数即可。

    2.使用While进行轮询

    伪代码如下,类似于非阻塞延时的第一种形式

        bool validValueReceived = false;
        // 设置超时时间为5秒
        const int timeoutSeconds = 5;
        
        QTime startTime = QTime::currentTime().addSecs(timeoutSeconds);
        while (!validValueReceived)
        {
            // 检查是否超时
            QTime currentTime = QTime::currentTime();
            if (startTime.secsTo(currentTime) >= timeoutSeconds)
            {
                qDebug() << "超时未获取有效激光测距值";
                break;
            }
    
            // 获取激光测距模块的值
            double distance = getDistanceValue();
            if (distance > 0)
            {
                qDebug() << "有效的激光测距值:" << distance;
                validValueReceived = true;
            }
    
            // 处理事件,确保应用程序响应
            QCoreApplication::processEvents();
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26

    参考:
    QEventLoop
    Qt线程基础

  • 相关阅读:
    SimSiam-Exploring Simple Siamese Pepresentation Learning
    开发板uboot与virtualbox虚拟机、windows11网络互通
    Linux input输入子系统
    java毕业生设计业余足球队服务平台计算机源码+系统+mysql+调试部署+lw
    052-第三代软件开发-系统监测
    普罗米修斯Prometheus安装和使用
    【Tomcat】为Tomcat服务配置本地Apr库以提升性能
    31C++编程提高篇----2、类模板原理
    Redis----布隆过滤器
    java项目-第160期ssm大学生校园兼职系统_ssm毕业设计_计算机毕业设计
  • 原文地址:https://blog.csdn.net/weixin_41502364/article/details/136484092