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

news/2024/11/18 16:26:04/

问题描述

进程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)<msec);
}

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

它使用了一个无限循环,以不断检查当前时间和起始时间之间的差距是否达到指定的毫秒数。在函数开始时,通过 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:"<<current.toString("hh:mm:ss.zzz");//10ms以后QTime currentNew = QTime::fromString("23:59:59.910","hh:mm:ss.zzz");qDebug()<<"10ms later currentNew:"<< currentNew.toString("hh:mm:ss.zzz");qDebug()<<"10ms later break res:"<<!(current.msecsTo(currentNew)<500);//250ms以后currentNew = QTime::fromString("00:00:00.150","hh:mm:ss.zzz");qDebug()<<"250ms later currentNew:"<< currentNew.toString("hh:mm:ss.zzz");qDebug()<<"250ms later break res:"<<!(current.msecsTo(currentNew)<500);//一秒以后currentNew = QTime::fromString("00:00:00.900","hh:mm:ss.zzz");qDebug()<<"1s later currentNew:"<< currentNew.toString("hh:mm:ss.zzz");qDebug()<<"1s later break res:"<<!(current.msecsTo(currentNew)<500);qDebug()<<"----------testSleep2 finish--------";
}

代码首先定义了一个起始时间,即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()<reachTime){//如果当前的系统时间尚未达到我们设定的时刻,就让Qt的应用程序类执行默认的处理,//以使程序仍处于响应状态。一旦到达了我们设定的时刻,就跳出该循环,继续执行后面的语句。QApplication::processEvents(QEventLoop::AllEvents,100);}
}

它的实现方式是计算出当前时间加上需要延时的毫秒数后得到的一个延时结束时间点,然后使用一个 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); //假设延时500msqDebug()<<"reachTime:"<<reachTime.toString("hh:mm:ss.zzz");//10ms以后QTime currentNew = QTime::fromString("23:59:59.910","hh:mm:ss.zzz");qDebug()<<"10ms later currentNew:"<< currentNew.toString("hh:mm:ss.zzz");qDebug()<<"10ms later break res:"<<!(currentNew<reachTime);//250ms以后currentNew = QTime::fromString("00:00:00.150","hh:mm:ss.zzz");qDebug()<<"250ms later currentNew:"<< currentNew.toString("hh:mm:ss.zzz");qDebug()<<"250ms later break res:"<<!(currentNew<reachTime);//1s以后currentNew = QTime::fromString("00:00:00.900","hh:mm:ss.zzz");qDebug()<<"1s later currentNew:"<< currentNew.toString("hh:mm:ss.zzz");qDebug()<<"1s later break res:"<<!(currentNew<reachTime);qDebug()<<"----------testSleep1 finish--------";
}

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

---------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()<reachTime){//如果当前的系统时间尚未达到我们设定的时刻,就让Qt的应用程序类执行默认的处理,//以使程序仍处于响应状态。一旦到达了我们设定的时刻,就跳出该循环,继续执行后面的语句。QApplication::processEvents(QEventLoop::AllEvents,100);}
}//延时功能4
void sleep4(unsigned int msec){//currnentTime 返回当前时间QDateTime n = QDateTime::currentDateTime();QDateTime now;do{//currnentTime 返回当前时间now = QDateTime::currentDateTime();}while(n.msecsTo(now)<msec);//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:"<<reachTime.toString(dateTimeFormat);//10ms后QDateTime currentNew = QDateTime::fromString("2023-06-07 23:59:59.910",dateTimeFormat);qDebug()<<"10ms later currentNew:"<< currentNew.toString(dateTimeFormat);qDebug()<<"10ms later break res:"<<!(currentNew<reachTime);//250ms后currentNew = QDateTime::fromString("2023-06-08 00:00:00.150",dateTimeFormat);qDebug()<<"250ms later currentNew:"<< currentNew.toString(dateTimeFormat);qDebug()<<"250ms later break res:"<<!(currentNew<reachTime);//1s后currentNew = QDateTime::fromString("2023-06-08 00:00:00.900",dateTimeFormat);qDebug()<<"1s later currentNew:"<< currentNew.toString(dateTimeFormat);qDebug()<<"1s later break res:"<<!(currentNew<reachTime);qDebug()<<"----------testSleep3 finish--------";
}//sleep4跨天测试
void testSleep4(){QString dateTimeFormat = "yyyy-MM-dd hh:mm:ss.zzz";qDebug()<<"----------testSleep4 start--------";QDateTime current = QDateTime::fromString("2023-06-07 23:59:59.900",dateTimeFormat);qDebug()<<"current Time:"<<current.toString(dateTimeFormat);//10ms以后QDateTime currentNew = QDateTime::fromString("2023-06-07 23:59:59.910",dateTimeFormat);qDebug()<<"10ms later currentNew:"<< currentNew.toString(dateTimeFormat);qDebug()<<"10ms later break res:"<<!(current.msecsTo(currentNew)<500);//250ms以后currentNew = QDateTime::fromString("2023-06-08 00:00:00.150",dateTimeFormat);qDebug()<<"250ms later currentNew:"<< currentNew.toString(dateTimeFormat);qDebug()<<"250ms later break res:"<<!(current.msecsTo(currentNew)<500);//一秒以后currentNew = QDateTime::fromString("2023-06-08 00:00:00.900",dateTimeFormat);qDebug()<<"1s later currentNew:"<< currentNew.toString(dateTimeFormat);qDebug()<<"1s later break res:"<<!(current.msecsTo(currentNew)<500);qDebug()<<"----------testSleep4 finish--------";
}

输出如下:
----------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 毫秒,以避免阻塞太长时间。

结束

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

完整代码如下:

#include "mainwindow.h"#include <QApplication>
#include <QCoreApplication>
#include <QTime>
#include <QDateTime>
#include <QDebug>//延时功能1
void sleep1(unsigned int msec){//currnentTime 返回当前时间 用当前时间加上我们要延时的时间msec得到一个新的时刻QTime reachTime = QTime::currentTime().addMSecs(msec);//用while循环不断比对当前时间与我们设定的时间while(QTime::currentTime()<reachTime){//如果当前的系统时间尚未达到我们设定的时刻,就让Qt的应用程序类执行默认的处理,//以使程序仍处于响应状态。一旦到达了我们设定的时刻,就跳出该循环,继续执行后面的语句。QApplication::processEvents(QEventLoop::AllEvents,100);}
}//sleep1跨天测试
void testSleep1(){qDebug()<<"----------testSleep1 start--------";QTime current = QTime::fromString("23:59:59.900","hh:mm:ss.zzz");QTime reachTime = current.addMSecs(500); //假设延时500msqDebug()<<"reachTime:"<<reachTime.toString("hh:mm:ss.zzz");//10ms以后QTime currentNew = QTime::fromString("23:59:59.910","hh:mm:ss.zzz");qDebug()<<"10ms later currentNew:"<< currentNew.toString("hh:mm:ss.zzz");qDebug()<<"10ms later break res:"<<!(currentNew<reachTime);//250ms以后currentNew = QTime::fromString("00:00:00.150","hh:mm:ss.zzz");qDebug()<<"250ms later currentNew:"<< currentNew.toString("hh:mm:ss.zzz");qDebug()<<"250ms later break res:"<<!(currentNew<reachTime);//1s以后currentNew = QTime::fromString("00:00:00.900","hh:mm:ss.zzz");qDebug()<<"1s later currentNew:"<< currentNew.toString("hh:mm:ss.zzz");qDebug()<<"1s later break res:"<<!(currentNew<reachTime);qDebug()<<"----------testSleep1 finish--------";
}//延时功能2
void sleep2(unsigned int msec){//currnentTime 返回当前时间QTime n = QTime::currentTime();QTime now;do{//currnentTime 返回当前时间now = QTime::currentTime();}while(n.msecsTo(now)<msec);
}//sleep2跨天测试
void testSleep2(){qDebug()<<"----------testSleep2 start--------";QTime current = QTime::fromString("23:59:59.900","hh:mm:ss.zzz");qDebug()<<"current Time:"<<current.toString("hh:mm:ss.zzz");//10ms以后QTime currentNew = QTime::fromString("23:59:59.910","hh:mm:ss.zzz");qDebug()<<"10ms later currentNew:"<< currentNew.toString("hh:mm:ss.zzz");qDebug()<<"10ms later break res:"<<!(current.msecsTo(currentNew)<500);//250ms以后currentNew = QTime::fromString("00:00:00.150","hh:mm:ss.zzz");qDebug()<<"250ms later currentNew:"<< currentNew.toString("hh:mm:ss.zzz");qDebug()<<"250ms later break res:"<<!(current.msecsTo(currentNew)<500);//一秒以后currentNew = QTime::fromString("00:00:00.900","hh:mm:ss.zzz");qDebug()<<"1s later currentNew:"<< currentNew.toString("hh:mm:ss.zzz");qDebug()<<"1s later break res:"<<!(current.msecsTo(currentNew)<500);qDebug()<<"----------testSleep2 finish--------";
}//延时功能3
void sleep3(unsigned int msec){//currnentTime 返回当前时间 用当前时间加上我们要延时的时间msec得到一个新的时刻QDateTime reachTime = QDateTime::currentDateTime().addMSecs(msec);//用while循环不断比对当前时间与我们设定的时间while(QDateTime::currentDateTime()<reachTime){//如果当前的系统时间尚未达到我们设定的时刻,就让Qt的应用程序类执行默认的处理,//以使程序仍处于响应状态。一旦到达了我们设定的时刻,就跳出该循环,继续执行后面的语句。QApplication::processEvents(QEventLoop::AllEvents,100);}
}//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:"<<reachTime.toString(dateTimeFormat);//10ms后QDateTime currentNew = QDateTime::fromString("2023-06-07 23:59:59.910",dateTimeFormat);qDebug()<<"10ms later currentNew:"<< currentNew.toString(dateTimeFormat);qDebug()<<"10ms later break res:"<<!(currentNew<reachTime);//250ms后currentNew = QDateTime::fromString("2023-06-08 00:00:00.150",dateTimeFormat);qDebug()<<"250ms later currentNew:"<< currentNew.toString(dateTimeFormat);qDebug()<<"250ms later break res:"<<!(currentNew<reachTime);//1s后currentNew = QDateTime::fromString("2023-06-08 00:00:00.900",dateTimeFormat);qDebug()<<"1s later currentNew:"<< currentNew.toString(dateTimeFormat);qDebug()<<"1s later break res:"<<!(currentNew<reachTime);qDebug()<<"----------testSleep3 finish--------";
}//延时功能4
void sleep4(unsigned int msec){//currnentTime 返回当前时间QDateTime n = QDateTime::currentDateTime();QDateTime now;do{//currnentTime 返回当前时间now = QDateTime::currentDateTime();}while(n.msecsTo(now)<msec);//currentDateTime 返回当前时间
}//sleep4跨天测试
void testSleep4(){QString dateTimeFormat = "yyyy-MM-dd hh:mm:ss.zzz";qDebug()<<"----------testSleep4 start--------";QDateTime current = QDateTime::fromString("2023-06-07 23:59:59.900",dateTimeFormat);qDebug()<<"current Time:"<<current.toString(dateTimeFormat);//10ms以后QDateTime currentNew = QDateTime::fromString("2023-06-07 23:59:59.910",dateTimeFormat);qDebug()<<"10ms later currentNew:"<< currentNew.toString(dateTimeFormat);qDebug()<<"10ms later break res:"<<!(current.msecsTo(currentNew)<500);//250ms以后currentNew = QDateTime::fromString("2023-06-08 00:00:00.150",dateTimeFormat);qDebug()<<"250ms later currentNew:"<< currentNew.toString(dateTimeFormat);qDebug()<<"250ms later break res:"<<!(current.msecsTo(currentNew)<500);//一秒以后currentNew = QDateTime::fromString("2023-06-08 00:00:00.900",dateTimeFormat);qDebug()<<"1s later currentNew:"<< currentNew.toString(dateTimeFormat);qDebug()<<"1s later break res:"<<!(current.msecsTo(currentNew)<500);qDebug()<<"----------testSleep4 finish--------";
}int main(int argc, char *argv[])
{QApplication a(argc, argv);testSleep1();testSleep2();testSleep3();testSleep4();MainWindow w;w.show();return a.exec();
}#if 0
----------testSleep1 start--------
reachTime: "00:00:00.400"
10ms currentNew: "00:00:00.150"
break res: true
currentNew: "00:00:00.900"
break res: false
----------testSleep1 finish--------
#endif

http://www.ppmy.cn/news/258149.html

相关文章

【CesiumJS入门】(3)ImageryLayer之图层卷帘

前言 上一篇博客简单得介绍了影像图层并成功在视图上加载出来了&#xff0c;而今天我们来实现一个简单的可视化效果&#xff0c;影像图层卷帘。 前置知识&#xff1a;Cesium 事件详解&#xff08;鼠标事件、相机事件、键盘事件、场景触发事件&#xff09;_cesium点击事件_GIS…

《C++高级编程》读书笔记(四:设计专业的C++程序)

1、参考引用 C高级编程&#xff08;第4版&#xff0c;C17标准&#xff09;马克葛瑞格尔 2、建议先看《21天学通C》 这本书入门&#xff0c;笔记链接如下 21天学通C读书笔记&#xff08;文章链接汇总&#xff09; 1. 程序设计概述 在启动新程序&#xff08;或已有程序的新功能&…

SpringBoot整合邮箱验证码实现用户注册

唠嗑部分 今天我们来分享一下在系统开发过程中&#xff0c;如何使用验证码来验证用户并完成用户注册 首先来看一下成品界面展示 说一下以上注册功能的设计&#xff1a; 用户手动输入用户名(全数据库唯一)、密码、确认密码、邮箱地址(单个邮箱最多可注册3个用户)、正确的邮箱…

触屏笔哪个牌子好用?高性价比的电容笔推荐

事实上&#xff0c;苹果原本的电容笔和普通的电容笔最大的区别就是重力压感&#xff0c;市面上还没有一款能像apple pencil这样拥有着独特重力压感的电容笔。但是因为苹果的pencil太贵&#xff0c;很多人都把注意力都放在平替电容笔一边&#xff0c;平替电容笔的性能也在不断提…

哪个牌子的电容笔好,电容笔牌子排行

电容笔作为现在当下热销的数码配件&#xff0c;由于原装的价格太高了&#xff0c;所以市面上出现许多品牌的平替&#xff0c;在无数品牌商加入其中&#xff0c;导致许多小伙伴购选时难度加大&#xff0c;而大部分的品牌都是大同小异&#xff0c;看得眼花缭乱&#xff0c;更不知…

哪个牌子集成灶好用,这款登上《消费主张》栏目的集成灶品牌火了

如今&#xff0c;随着集成灶行业的发展&#xff0c;集成灶品牌众多&#xff0c;产品也比较多样化。因此市场上也出现了十大集成灶排名等供广大消费者参考的权威榜单。那么在这些知名品牌中&#xff0c;哪个牌子集成灶好用&#xff1f;集成灶有哪些功能是值得重点关注的呢&#…

Python time 模块

time 是python的内置模式,使用的时候需要import time 引入 time 的几个主要函数如下 import time# 当前时间戳 print("time:", time.time()) # 打印结果time: 1686107990.798039 # 返回可读形式的时间 print("ctime:", time.ctime()) # 打印结果ctime:…

PWA:Service Worker实现离线访问、域名失效启用备用域名......

介绍下Service Worker service worker 运行在独立的线程&#xff0c;可以拦截和处理网络请求&#xff0c;以实现离线缓存、推送通知和后台同步等功能。具体可以看MDN的介绍 怎么使用Service Worker 1、注册脚本 // useServiceWorker.ts navigator.serviceWorker?.register…