Qt延时函数跨天导致的死循环问题

问题描述

进程A给进程B发送数据,进程B收到数据后进行处理。但是奇怪的时跨天的情况下,会偶尔出现进程B收不到数据的情况,重启进程A和进程B后收数据正常(进程B管理着进程A,会一起重启,因此不清楚具体那个软件有问题)。

问题分析和定位

假设:

1. 发数据有问题

2. 收数据有问题

3. 数据处理有问题

通过日志观察,发现进程B收到的最后一帧数据是GPS数据,因此排查GPS数据处理逻辑。发现延时函数存在bug,导致了跨天时概率性的死循环,进而该线程无法继续收数据

延时函数实现

延时函数的代码如下所示(之前在网上抄来的,已经使用了多年了,哭死)

//延时功能2
void sleep2(unsigned int msec){
    //currnentTime 返回当前时间
    QTime n = QTime::currentTime();
    QTime now;
    do
    {
        //currnentTime 返回当前时间
        now = QTime::currentTime();
    }while(n.msecsTo(now)

这段代码为实现延时功能提供了一种简单的解决方案。

它使用了一个无限循环,以不断检查当前时间和起始时间之间的差距是否达到指定的毫秒数。在函数开始时,通过 QTime::currentTime() 获取当前时间,并将其存储在一个变量 n 中。随后,继续做循环,每次在循环内部重新获取当前时间,并计算出两个时间之间的毫秒数差异,然后判断是否已经达到要求的总延迟时间。

如果条件满足,即当前时间与起始时间之差大于等于指定的毫秒数,就会退出循环并继续执行下一步操作;否则,就会一直保持在循环中。这里采用了 do-while 循环来确保至少运行一次循环体。

然后我们写一段测试代码,看看该循环是否能成功退出:

//sleep2跨天测试
void testSleep2(){
    qDebug()<<"----------testSleep2 start--------";

    QTime current = QTime::fromString("23:59:59.900","hh:mm:ss.zzz");
    qDebug()<<"current Time:"<

代码首先定义了一个起始时间,即23:59:59.900,然后经过一些时间跨度(10毫秒、250毫秒和1秒),计算出新的时间并打印输出。对于每个结果,它还将调用 current.msecsTo(currentNew) 来计算两个时间之间的毫秒数差值,并检查是否小于500毫秒。如果结果小于500毫秒,程序就会中断执行。

这个测试旨在检查 msecsTo() 方法是否能够正常处理时间跨度。换句话说,它测试 msecsTo() 在涉及跨越一天的时间差时是否可以产生正确的结果。测试中使用的时间戳非常接近午夜,以便确保可以生成正确的测试条件。

输出结果:

----------testSleep2 start--------
current Time: "23:59:59.900"
10ms later currentNew: "23:59:59.910"
10ms later break res: false
250ms later currentNew: "00:00:00.150"
250ms later break res: false
1s later currentNew: "00:00:00.900"
1s later break res: false
----------testSleep2 finish--------

结论:过了零点以后,msecsTo()返回的是一个负数,所以导致了死循环。

延时函数另一种实现

网上常见的还有一种写法,代码如下。它能不能通过测试呢?我又试了试。

//延时功能1
void sleep1(unsigned int msec){
    //currnentTime 返回当前时间 用当前时间加上我们要延时的时间msec得到一个新的时刻
    QTime reachTime = QTime::currentTime().addMSecs(msec);
    //用while循环不断比对当前时间与我们设定的时间
    while(QTime::currentTime()

它的实现方式是计算出当前时间加上需要延时的毫秒数后得到的一个延时结束时间点,然后使用一个 while 循环来不断比对当前时间是否早于该结束时间点。

在循环内部,使用 QApplication::processEvents() 方法强制进行事件处理,以确保程序仍然处于响应状态。这个方法会让 Qt 应用程序类执行默认的处理,并检查是否有新的事件需要处理。如果没有,则等待指定的毫秒数(这里是100毫秒),然后再次检查。如果有新的事件,则立即处理,并返回到循环继续比对时间。

循环会一直运行,直到当前时间晚于或等于设定的延时结束时间点,此时就会跳出循环并继续执行后面的语句。

然后我们同样写一段测试代码,看看该循环是否能成功退出:

//sleep1跨天测试
void testSleep1(){
    qDebug()<<"----------testSleep1 start--------";

    QTime current = QTime::fromString("23:59:59.900","hh:mm:ss.zzz");
    QTime reachTime = current.addMSecs(500); //假设延时500ms
    qDebug()<<"reachTime:"<

代码设计与上面相似。输出结果如下:

---------testSleep1 start--------
reachTime: "00:00:00.400"
10ms later currentNew: "23:59:59.910"
10ms later break res: true
250ms later currentNew: "00:00:00.150"
250ms later break res: false
1s later currentNew: "00:00:00.900"
1s later break res: true
----------testSleep1 finish--------

结论:跨天可以正常跳出循环,不会出现死循环。但是结束时间是下一天情况下,并不能起到循环的作用,10ms的时候就以及跳出循环了。

可取的方法

很容易想到,上面两个函数之所以异常,直接原因是跨天的情况下丢失了日期的信息。解决方法也很简单,将QTime换成QDateTime就可以解决问题。

代码如下:

//延时功能3
void sleep3(unsigned int msec){
    //currnentTime 返回当前时间 用当前时间加上我们要延时的时间msec得到一个新的时刻
    QDateTime reachTime = QDateTime::currentDateTime().addMSecs(msec);
    //用while循环不断比对当前时间与我们设定的时间
    while(QDateTime::currentDateTime()

测试代码如下:

//sleep3跨天测试
void testSleep3()
{
    QString dateTimeFormat = "yyyy-MM-dd hh:mm:ss.zzz";

    qDebug()<<"----------testSleep3 start--------";
    QDateTime current = QDateTime::fromString("2023-06-07 23:59:59.900",dateTimeFormat);
    QDateTime reachTime = current.addMSecs(500);;
    qDebug()<<"reachTime:"<

输出如下:
----------testSleep3 start--------
reachTime: "2023-06-08 00:00:00.400"
10ms later currentNew: "2023-06-07 23:59:59.910"
10ms later break res: false
250ms later currentNew: "2023-06-08 00:00:00.150"
250ms later break res: false
1s later currentNew: "2023-06-08 00:00:00.900"
1s later break res: true
----------testSleep3 finish--------


----------testSleep4 start--------
current Time: "2023-06-07 23:59:59.900"
10ms later currentNew: "2023-06-07 23:59:59.910"
10ms later break res: false
250ms later currentNew: "2023-06-08 00:00:00.150"
250ms later break res: false
1s later currentNew: "2023-06-08 00:00:00.900"
1s later break res: true
----------testSleep4 finish--------

仍然存在的问题

虽然这种方法可以有效实现延时功能,但是在大量延时的情况下会导致 CPU 飙升,从而影响程序的性能和稳定性。建议程序中尽量避免使用这种延时方式,并考虑使用 QTimer 等 Qt 的定时器功能代替。

其他实现方法(我没用过)

QTimer和QEventLoop结合,但是精度不一定能达到1ms。

void sleep(unsigned int msec)
{
    QEventLoop loop;
    QTimer::singleShot(msec, &loop, &QEventLoop::quit);
    loop.exec();
}

另一种是基于QElapsedTimer。这种方式是比较常规的实现方式,尤其适用于需要更高精度的场景,它通过 Qt 的事件循环机制来实现延迟操作,并支持处理界面事件等更加复杂的交互操作。在实际中需要根据场景选择合适的实现方式。

void sleep(unsigned int msec)
{
    QElapsedTimer timer;
    timer.start();
    while (timer.elapsed() < msec) {
        QCoreApplication::processEvents(QEventLoop::AllEvents, 100);
    }
}

这个函数创建了一个 QElapsedTimer 对象,用于计算时间间隔。然后利用一个循环不断处理事件,并检查 QElapsedTimer 对象的经过时间是否已经超过了指定的延时时间。

需要注意的是,为了避免因为某些原因导致事件处理停滞,这里将 QCoreApplication::processEvents 的参数设置成了 QEventLoop::AllEvents,以确保能够处理所有事件(包括渲染、resize 等)。同时在每次循环中都限制了最大等待时间为 100 毫秒,以避免阻塞太长时间。

补充说明(20230612)

QElapsedTimerQDateTime是用于计时的两个不同的类,它们各具特点,可以根据实际需求采用不同的方式。

延时是指程序中需要暂停执行一段时间,通常使用这种方式来控制程序运行速度或者等待某些操作完成。使用QElapsedTimerQDateTime来实现延时都是可行的,但是具有不同的特点:

  1. QElapsedTimer精度较高,适合计算较短的时间间隔。它内部利用CPU高精度计时器,可以获得更准确的时间戳。因此,在需要进行从微秒/纳秒级别的计算时可以优选使用QElapsedTimer。

  2. QDateTime则主要用于处理与日期和时间相关的功能。在应用场景中,如果需要将延迟的时长转换成指定的日期时间格式,或是进行周、月、年等的日期计算,则使用QDateTime会更加方便。

综上所述,对于需要进行精确计算的需求可以选择使用QElapsedTimer,而对于以时间为单位的特殊需求,比如计算涉及具体日期时间的延时,可以使用QDateTime来实现。

结束

最后不得不说一下,循环是个危险的操作,要多加注意。

完整代码如下:

#include "mainwindow.h"

#include 
#include 
#include 
#include 
#include 

//延时功能1
void sleep1(unsigned int msec){
    //currnentTime 返回当前时间 用当前时间加上我们要延时的时间msec得到一个新的时刻
    QTime reachTime = QTime::currentTime().addMSecs(msec);
    //用while循环不断比对当前时间与我们设定的时间
    while(QTime::currentTime()

你可能感兴趣的:(QT,c++,qt,开发语言)