QTimer计时速度不断加快问题和QT中show函数失效问题的思考和处理

本文探讨了QT应用程序中出现的两个问题:QTimer计时速度不断加快及返回主界面时界面卡顿。前者通过避免重复连接信号与槽解决,后者则通过临时显示数字键盘使界面恢复正常。

使用QT所遇问题之QTimer和show

一。 QTimer计时速度不断加快问题

1. 问题的发现

项目需要添加“延时登录”功能,但是在测试过程中发现此功能会时常失效。

2.问题解决的思路

(1)梳理整个功能实现的逻辑

退出设置界面的时候,打开定时器,设置相关参数logout_time_count(此参数决定延时时间),定时器时间到达之后进入中断,中断中logout_time_count–,直到logout_time_count值为0,将定时器停止,并退出登录。直到下一次退出设置界面时,再开始定时器的计时。

(2)监控进入定时器中断的次数

通过LOGD方式实时监控进入定时器中断的次数,发现每秒(定时器时间为1s)会多次进入中断函数,判断可能是定时器的值没有刷新导致不断进入定时器中断函数。(此处判断错误。事实上,如果定时器的计数没有清零和刷新,定时器不会多次进入中断而是会延长进入中断的时间)

(3)跟踪参数logout_time_count的值

通过LOGD的方式实时显示logout_time_count的值于日志上,观察其变化规律,发现每一次退出设置界面之后logout_time_count参数的值下降的速度就会增加一倍。 此现象与并行运行的情况非常相似,所以猜想是否是定时器并行运行导致的速度计时速度加快。

(4)QTimer定时器的timeout函数的并行运行

查找资料,发现QTimer的timeout多函数的并行执行这个作者遇到的问题与我的问题相似,受这篇博文启发,发现问题的核心就是connect函数的问题。因为原程序在逻辑上是在每次退出设置界面的时候都运行一次connect函数:connect(&logout_timer, SIGNAL(timeout()), this, SLOT(logout_timer_slots()));而每一次执行这行代码都会给定时器增加一个新的槽函数,以至于每次定时器中断都会并行运行多个槽函数,从而导致logout_time_count参数递减的速度加快(在槽函数中logout_time_count–)。

(5)修改程序

既然问题在于"每次退出设置界面的时候都会执行connect函数导致多个槽函数并行运行",那么就让程序只在第一次退出设置界面的时候才执行这行代码就可以解决问题了:

	if(is_fist_enter_settingwidgt==true)
	{
	    connect(&logout_timer, SIGNAL(timeout()), this, SLOT(logout_timer_slots()));
		is_fist_enter_settingwidgt = false;
	}

(6)总结

在QT中,如果多次执行connect函数可能会导致“当一个信号发出时会触发多个相同的槽函数并行运行”,而反过来讲,这并不一定是坏事,如果我们想要实时地并行执行某些函数的时候可以考虑用多次执行connect的方式来实现。

二。返回主窗口时界面卡住不动问题

1. 问题的发现和问题概要

在从设置界面退出到主窗口的时候,会不规律地出现“界面卡住不动,界面不刷新”的问题。初步判断是退出设置界面的时候发生线程冲突,导致主线程堵塞。

2. 问题解决的思路

(1)梳理在设置界面中按下返回键的一瞬间的逻辑以及程序运行了哪些代码。

(2)一般情况系统出现卡死的原因

一是系统在执行耗时程序甚至陷入了死循环导致卡死;二是出现内存泄漏导致主线程堵塞。

(3)监控程序运行

通过输出日志的方式来监控程序运行,找出程序卡死的位置。结果发现,退出到主窗口的整个过程都顺利进行,没有任何地方出现卡死的地方。也就是程序已经执行了“进入主窗口”的代码。

(4)查看线程状态

查看线程状态的方法可参考我之前的博文2020.9.28(查看线程状况、对象的概念、AutoLock类的作用),但是由于不能实时看得到线程的状态,而且我本身对于线程的知识也不足,所以这个过程中没有得到很大的收获,也没找到问题出现的原因。

但是,在操作系统的时候发现,虽然画面卡住了,但是主窗口中的一些功能还是能够实现,这应该可以说明主线程并没有卡住,而是主线程一些功能失效了。

(5)找出主窗口哪些功能仍然可用,哪些功能不能使用

1)仍然可用的功能:遥控键盘控制,控件事件,可以凭借记忆操控遥控重新进入设置界面。
2)失效的功能:主窗口的显示(包括键盘事件触发显示其他窗口的功能也失效),QopenGL渲染功能失效等。

在这个过程中意外发现:在登录弹出数字键盘的一瞬间,主窗口所有功能恢复,而且在卡住时进行的涉及OpenGL的键盘操作都在这一瞬间起效。

(6)跟踪QOpenGL功能的整个过程

由上面现象可知,主要失效的就是显示和渲染部分的功能,于是进行QOpenGL功能的跟踪,梳理这部分的逻辑,在每个节点设置一个日志输出,运行程序就可以跟踪这部分程序运行的过程。
然而在我梳理完这部分的逻辑且加上日志打印信息并运行的时候,发现程序根本没有进入paintGL函数中。(QOpenGL的机制就是每执行一次update()函数,就会自动运行paintGL中的代码,而本系统在每次视频流来的时候都会执行一次update()函数)【此处效率不高,应当按顺序一部分一部分地跟踪,而不是一下子给整个过程都加上了日志信息,从而浪费了相当一部分的时间

(7)监控从接收视频流到进入paintGL的过程

发现视频流成功接收,发出信号后,也进入了对应的槽函数,在这个槽函数中也运行了update()代码,所以根据QOpenGL的机制,理应进入paintGL中。所以现在的问题就是:为何执行了update代码,但是没有按照QOpenGL的机制进入painGL函数。

(8)寻找“QT调用update后不进paintGL”的原因

原来,update函数控制窗口重绘(重绘事件发出就能进入paintGL函数)的过程是:窗口系统为了提高性能,会尽量减少绘制次数和范围,所以默认情况下,只有窗口内容显示出来的时候才进行一次绘制,包括窗口被遮挡的部分重新显现。update和repaint就是主动通知系统绘制窗口内容。区别在于,update只是在内部数据中做一个标记,要等到主线程进行到下一次绘制时才进行绘制,所以多次调用update是没有意义的,只是重复设置绘制标记,并不会马上进行绘制,而且同样要等到你的代码走完之后走到绘制时才能看到结果

简单来说,update函数只是设置了一个绘制标志,需要先显示窗口,这个绘制标志才会生效,从而进入paintGL函数。

所以,现在的问题就变成:为何明明执行了show函数,窗口却没有显示呢?

(9)寻找“show函数失效的原因”

经过一段时间的资料查找后,还是没能知道show函数失效的原因,各位看到此处的朋友,如果有思路可以提供一下,我将不胜感激!!

(10)寻找“数字键盘出来的一瞬间主窗口恢复正常”的原因

1)数字键盘的出现可以使得主窗口的显示恢复正常,但是其他对话框(比如登录对话框)的出现并不能使得主窗口的显示恢复正常。
2)排查了数字键盘的有关功能,将其与登录对话框做对比,还是没能发现究竟数字键盘有何特殊可以使得主窗口恢复正常。
3)将show函数注释之后,发现在退出的时候就会出现一样界面卡住的问题,这又一次证明是show函数出了问题。在数字键盘出现的一瞬间,主窗口还是会显示出来,但是这个时候,界面是不动的(就也就是说OpenGL功能没有恢复),说明数字键盘的出现,只是将,本来执行了的但因为某种不知名原因而没有生效的show函数,给生效了。而不是数字键盘具有show函数的功能。或者说数字键盘的功能是完成了从显示设置界面到显示主窗口的切换

(11)在退出设置界面到主窗口的过程中显示数字键盘并马上隐藏

既然数字键盘显示之后可以使得主窗口正常显示,那么就在退出设置界面到主窗口的过程中,在show函数之后,显示数字键盘并马上隐藏,不就可以解决问题了?实践过后,事实证明这个办法可行,但是会有一个问题,在界面卡住问题的时候虽然可以自动解除,但是会有数字键盘一闪而过(如果没有出现界面卡住问题,平常不会有闪屏问题)。

3.总结

(1)问题出现的原因主要在show函数,执行了show函数却不知道什么原因show函数没有生效。

(2)问题通过“显示数字键盘并马上隐藏”的方法得到规避。

(3)数字键盘的出现,只是将,本来执行了的但因为某种不知名原因而没有生效的show函数,给生效了。而不是数字键盘具有show函数的功能。或者说数字键盘的功能是完成了从显示设置界面到显示主窗口的切换。

(4)数字键盘与show函数的内在逻辑关系还不能明确知悉,还有待探究,各位看到这里的朋友有什么想法思路,可以分享一下,我将不胜感激。

<think>我们正在讨论QT中的QTimer定时器,特别是关于暂停、开启重新启动的使用方法。根据提供的引用资料,我们可以总结如下:1.**开启定时器**:使用`start(intmsec)`函数,参数是毫秒为单位的间隔时间。定时器开始后,会每隔指定时间发出`timeout()`信号。2.**暂停定时器**:使用`stop()`函数。注意,在Linux版本中,调用`stop()`后再调用`start()`,定时器不会重新计时,而是继续之前的计时状态(即从暂停的位置继续)。3.**重新启动定时器**:由于直接调用`stop()`后再`start()`并不能重置计时,所以需要采取其他方法:-方法一:使用`killTimer()`释放当前定时器,然后重新创建一个定时器。但注意,`killTimer()`是QObject的成员函数,通常用于通过`startTimer()`启动的定时器(即使用事件的方式)。对于QTimer对象,我们可以通过以下步骤:1.停止定时器:`timer->stop();`2.删除定时器对象:`deletetimer;`(如果之前是用new创建的)3.重新创建定时器并设置:`timer=newQTimer;`然后重新连接信号槽,再调用`start()`。-方法二:使用`QTimer::singleShot`实现单次触发,但这不是循环定时器。-方法三:使用`timerEvent`事件`startTimer`、`killTimer`(这是另一种定时器机制,不基于QTimer类)。另外,引用[3]中提到,QTimer在多线程情况下只能由创建线程调用,不能跨线程调用。下面我们给出具体的代码示例:###使用QTimer实现开启、暂停重新启动####开启暂停```cppQTimer*timer=newQTimer(this);//创建定时器,指定父对象以便自动管理内存connect(timer,&QTimer::timeout,this,&MyClass::onTimeout);//连接超时信号到槽函数//开启定时器,每隔1000毫秒触发一次timer->start(1000);//暂停定时器timer->stop();```####重新启动(重置计时)由于直接`stop()`后`start()`不能重置计时,我们需要先销毁再重建:```cpp//假设timer是成员变量,已经创建并连接过信号槽//停止并删除旧的定时器timer->stop();deletetimer;//注意:如果timer的父对象是this,则可以不显式delete,但这里我们要重新创建,所以先删除//重新创建定时器timer=newQTimer(this);connect(timer,&QTimer::timeout,this,&MyClass::onTimeout);//启动定时器,重新开始计时timer->start(1000);```或者,我们可以使用一个更简单的方法:先停止,然后通过改变定时器间隔来“重置”:```cpp//停止定时器timer->stop();//设置新的间隔时间(可以是原来的值)timer->setInterval(1000);//重新启动,此时会重置计时timer->start();```但是,根据引用[2]中的描述,在Linux上再次`start()`并不会重新计时。不过,根据Qt文档,调用`start()`会重启定时器,除非定时器已经运行。实际上,在最新的Qt中,`start()`在定时器停止后调用会重新开始计时。但引用[2]提到的问题可能是特定版本(或Linux)的bug。因此,为了确保重置,我们可以先改变间隔(即使改为相同的值)再启动:```cpp//停止定时器timer->stop();//改变间隔(即使相同,也会重置内部状态)timer->setInterval(timer->interval());//设置为当前间隔,实际上会重置//重新启动timer->start();```或者,我们可以使用`setSingleShot(true)`然后再`setSingleShot(false)`,但这样比较麻烦。###使用另一种定时器机制(timerEvent)引用[4]展示了使用`QObject`的定时器事件:```cpp//在类声明中声明事件处理函数protected:voidtimerEvent(QTimerEvent*event)override;//启动定时器inttimerId=startTimer(1000);//返回定时器ID//在事件处理函数中voidMyClass::timerEvent(QTimerEvent*event){if(event->timerId()==timerId){//处理定时事件}}//停止定时器killTimer(timerId);//此时timerId失效,若要重新启动,需要再次调用startTimer(1000)得到新的ID```这种方式下,重新启动需要先`killTimer`再`startTimer`,自然就重置了。###总结1.**开启**:`start(间隔时间)`2.**暂停**:`stop()`3.**重新启动(重置计时)**:-方法1(推荐):先`stop()`,然后调用`setInterval()`设置相同的间隔(以重置内部状态),再`start()`。-方法2:销毁旧的QTimer对象,重新创建并连接信号槽,再启动。-方法3:使用定时器事件(`startTimer``killTimer`),重新启动时先`killTimer`再`startTimer`。注意:在多线程环境中,定时器操作必须在创建它的线程中执行。§§相关问题§§1.如何验证QTimer在调用stop()后再start()是否重新计时?2.在Qt中,除了QTimer,还有哪些实现定时功能的方法?3.QTimer的精度如何?受哪些因素影响?4.在多线程程序中使用QTimer需要注意什么?</think>### QT QTimer定时器的暂停、开启重新启动使用方法 #### 1. **基本操作** - **开启定时器** 使用`start(int msec)`函数启动定时器,参数为毫秒级时间间隔: ```cpp QTimer *timer = new QTimer(this); connect(timer, &QTimer::timeout, this, &MyClass::handleTimeout); timer->start(1000); // 每隔1000毫秒触发一次 ``` - **暂停定时器** 调用`stop()`函数暂停计时: ```cpp timer->stop(); // 停止定时器,但保留当前状态 ``` #### 2. **重新启动的关键问题** 直接使用`stop()`后`start()`**不会重置计时**(在Linux等系统上会延续之前的计时状态)[^2]。需通过以下方法实现**完全重置**: - **方法1:销毁重建** 释放旧定时器并创建新实例: ```cpp timer->stop(); delete timer; // 释放旧定时器 // 重建定时器 timer = new QTimer(this); connect(timer, &QTimer::timeout, this, &MyClass::handleTimeout); timer->start(1000); // 重新开始计时 ``` - **方法2:使用`QTimer::singleShot`** 适合单次定时需求: ```cpp QTimer::singleShot(1000, this, &MyClass::handleTimeout); // 单次触发,自动重置 ``` - **方法3:结合`timerEvent`** 通过底层事件控制: ```cpp // 启动定时器(返回定时器ID) int timerId = startTimer(1000); // 停止并重置 killTimer(timerId); // 销毁当前定时器 timerId = startTimer(1000); // 重新创建 ``` #### 3. **注意事项** 1. **线程安全** QTimer必须在创建它的线程中操作,跨线程调用会导致未定义行为[^3]。 2. **状态保留** 调用`stop()`后内部计时状态不会清零,直接`start()`会延续暂停前的状态[^2]。 3. **精准重置** 若需完全重置计时,优先选择**销毁重建**或**`killTimer`/`startTimer`组合**。 #### 示例代码 ```cpp // 成员变量声明 QTimer *m_timer; // 初始化 m_timer = new QTimer(this); connect(m_timer, &QTimer::timeout, this, &MainWindow::updateUI); // 重新启动(完全重置) void MainWindow::restartTimer() { m_timer->stop(); delete m_timer; // 释放资源 m_timer = new QTimer(this); // 新建实例 connect(m_timer, &QTimer::timeout, this, &MainWindow::updateUI); m_timer->start(500); // 以500ms间隔重启 } ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值