热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

如何防止QT程序未响应

在日常的开发中,偶尔会执行一些可能很费时的代码,比如进行大规模的数据运算,生成或者拷贝文件,网络请求等,这些操作如果放在UI线程去做,一些操作,比如多点几次鼠标,或者是切换到其

       在日常的开发中,偶尔会执行一些可能很费时的代码,比如进行大规模的数据运算,生成或者拷贝文件,网络请求等,这些操作如果放在UI线程去做,一些操作,比如多点几次鼠标,或者是切换到其他程序再切换回来, 都很容易就会导致程序未响应,这是由于ui线程正在执行代码或者被阻塞住了,导致没法处理事件循环,系统认为你这个程序可能挂掉了,就会出现那个未响应提示,然后弹窗问用户,要不要强制干掉这个程序.

        优秀的软件这方面都会处理得比较好,会给出一些进度条之类的,而在这方面我最赞赏的就是Android,它会禁止你在ui线程里面做费时操作,比如网络请求,一在ui线程请求网络,立马挂掉,逼着你去实现异步的网络请求,从而保证app的流畅度,早期的Android不会这样的,估计是因为卡顿被骂多了,而开发者又不自觉,不得已才做出这样的限制的吧.Windows没有做出这样的限制,但是也要自觉,避免未响应情况的发生,那么,如何避免呢?

        未响应是由于事件没有及时处理导致的,所以是跟消息循环有关的,那么为了避免未响应,则有两个思路:

        1.程序执行中间,驱动一下事件循环;

        2.费时操作放到子线程,主线程空跑着,消息循环自然没有被阻塞.

        举例说明,比如我们在界面上放了一个按钮,然后按钮的点击事件执行循环运算

int sum = 0;
for (int i = 0;i <100; i++)
{
    ui.label->setText(QString::number(sum));
    for (int j = 0; j <1000000000; j++)
    {
        sum+= (i*j) & 3;
    }
    sum += i & 3;
}

        这个循环会一直占用着cpu,虽然第一层循环会设置label的值,但是在两层循环结束签并不能看到中间结果的显示,而是等循环完了才显示最后的结果(假设放一个进度条,同理,并不能看到进度的变化,而是一直是0%,最后一下子到100%).

        按照第一种思路,我们可以使用QApplication的processEvents方法来进行中间的消息循环驱动,改动代码如下:

int sum = 0;
for (int i = 0; i <100; i++)
{
	ui.label->setText(QString::number(sum));
	for (int j = 0; j <1000000000; j++)
	{
		sum += (i*j) & 3;
	}
	sum += i & 3;
      qApp->processEvents();
}

        唯一的区别在于第一层的后面加个qApp->processEvents();这个函数就是qt的强制事件驱动,其实它是有参数,接个枚举值,

enum ProcessEventsFlag {
        AllEvents = 0x00,
        ExcludeUserInputEvents = 0x01,
        ExcludeSocketNotifiers = 0x02,
        WaitForMoreEvents = 0x04,
        X11ExcludeTimers = 0x08,
        EventLoopExec = 0x20,
        DialogExec = 0x40
};

        默认是AllEvents,也就是驱动所有的事件,这个枚举值在很多的教程里面会推荐使用ExcludeUserInputEvents,对此的解释是说忽略用户的输入,从而避免按钮被点击两次,代码重复执行的类似情况,但是其实使用ExcludeUserInputEvents来达到忽略用户输入的目的并不是一个很好的办法,因为它只是此次事件驱动忽略掉而已,一旦你费时的代码执行完了,它会一次将多次的点击事件传给你,到时候可能就会导致大麻烦,故不推荐使用.正确的做法应该是在按钮点击之后,将其置为不可点击,也就是ui.pushButton->setEnabled(false);而processEvents采用默认的AllEvents的方式,这样pushButton本身就不接受这个点击事件了,而这个事件也被处理了,后期不会造成问题.但是倘若你使用ExcludeUserInputEvents,而循环前将setEnabled(false),循环后设置setEnabled(true);,则会循环后多次触发点击事件,连setEnabled都不起作用了 ,意外不意外,惊喜不惊喜?

        第二种思路,采用子线程,写异步代码会是一个问题,因为按钮按下去以后,等执行完是应该要有个提示,如果分开写,不在一个函数里面会使程序的复杂度变得很高,而且子线程的开辟,传参过去也会是个问题,好在c++11之后,我们有了std:: thread和lambda表达式及std:: condition_variable,这三个的组合,会让我们代码写起来很容易,代码如下:

int sum = 0;
ui.pushButton->setEnabled(false);
std::condition_variable kl_cv;
std::mutex kl_mtx;
std::unique_lock  kl_lck(kl_mtx);
std::thread kl_thread([&]()
{
	for (int i = 0; i <100; i++)
	{
		ui.label->setText(QString::number(sum));
		for (int j = 0; j <10000000; j++)
		{
			sum += (i*j) & 3;
		}
		sum += i & 3;
	}
	kl_cv.notify_all();
});
kl_thread.detach();
while (kl_cv.wait_for(kl_lck, std::chrono::milliseconds(100)) == std::cv_status::timeout)
{
	qApp->processEvents();
}
ui.pushButton->setEnabled(true);

         这里面使用了std:: thread来开启了一个新变量,然后使用lambda捕获全部引用,避免了我们一个个手动传参的麻烦事,最后使用std::condition_variable条件变量的wait_for来进行线程控制,每过100毫秒就进行一次事件驱动,跑起来后,很顺滑,完美!

         但是,这么多代码,难得每次都要重新写一次么?这样写起来也比较麻烦,那就把它写成两个宏吧,写完后代码简洁不少:

#define KeepLiveBegin {std::condition_variable kl_cv;std::mutex kl_mtx;std::unique_lock  kl_lck(kl_mtx);\
		std::thread kl_thread([&](){kl_mtx.lock(); kl_mtx.unlock();
#define KeepLiveEnd	kl_cv.notify_all();});kl_thread.detach(); \
		while (kl_cv.wait_for(kl_lck, std::chrono::milliseconds(100)) == std::cv_status::timeout){qApp->processEvents();}}
int sum = 0;
ui.pushButton->setEnabled(false);
KeepLiveBegin
for (int i = 0; i <100; i++)
{
	ui.label->setText(QString::number(sum));
	for (int j = 0; j <10000000; j++)
	{
		sum += (i*j) & 3;
	}
	sum += i & 3;
}
KeepLiveEnd
ui.pushButton->setEnabled(true);

        样比之前,仅仅多了两行宏定义,使用起来也方便不少.

        不过, setEnabled仍然看起来不舒服,如果界面上有很多的按钮,都要来一遍也不好维护,有个好办法可以很优雅地进行拦截,那就是使用事件过滤器,尤其是对QAppliction使用事件过滤器,能起到全局过滤的效果,我实现的代码如下:

class IgnoreEvent :public QObject
{
public:
	IgnoreEvent(QObject* obj=qApp)
	{
		m_obj = obj;
		m_obj->installEventFilter(this);
	}
	~IgnoreEvent()
	{
		m_obj->removeEventFilter(this);
	}
	bool eventFilter(QObject *obj, QEvent *event)
	{
		if (event->type() == QEvent::KeyPress || event->type() == QEvent::MouseButtonPress)
		{
			event->ignore();
			return true;
		}
		return QObject::eventFilter(obj, event);
	}
private:
	QObject* m_obj;
};

        然后点击事件的代码为

int sum = 0;
IgnoreEvent ignore;
KeepLiveBegin
for (int i = 0; i <100; i++)
{
	ui.label->setText(QString::number(sum));
	for (int j = 0; j <10000000; j++)
	{
		sum += (i*j) & 3;
	}
	sum += i & 3;
}
KeepLiveEnd

        相比于之前,去掉了setEnable 然后声明了IgnoreEventignore; IgnoreEvent类最主要的是installEventFilter和removeEventFilter,这里全局过滤掉了键盘的按键操作和鼠标的点击操作.也可以过滤特定界面的事件,当然也可以把这个写进宏定义里面,代码就会更简洁了.

        最后,完整代码如下:

#include 
#include 
#include 

#define KeepLiveBegin(ignoreevent) {IgnoreEvent* kl_ie =NULL; if(ignoreevent)kl_ie = new IgnoreEvent(); std::condition_variable kl_cv;std::mutex kl_mtx;std::unique_lock  kl_lck(kl_mtx);\
		std::thread kl_thread([&](){kl_mtx.lock(); kl_mtx.unlock();

#define KeepLiveEnd	kl_cv.notify_all();});kl_thread.detach(); \
		while (kl_cv.wait_for(kl_lck, std::chrono::milliseconds(100)) == std::cv_status::timeout){qApp->processEvents();}if(kl_ie!=NULL) delete kl_ie;}

class IgnoreEvent :public QObject
{
public:
	IgnoreEvent(QObject* obj=qApp)
	{
		m_obj = obj;
		m_obj->installEventFilter(this);
	}
	~IgnoreEvent()
	{
		m_obj->removeEventFilter(this);
	}
	bool eventFilter(QObject *obj, QEvent *event)
	{
		if (event->type() == QEvent::KeyPress || event->type() == QEvent::MouseButtonPress)
		{
			event->ignore();
			return true;
		}
		return QObject::eventFilter(obj, event);
	}
private:
	QObject* m_obj;
};

KeepLiveTest::KeepLiveTest(QWidget *parent)
	: QMainWindow(parent)
{
	ui.setupUi(this);
	connect(ui.pushButton, &QPushButton::clicked, [this](){
		int sum = 0;
		KeepLiveBegin(true)
		for (int i = 0; i <100; i++)
		{
			ui.label->setText(QString::number(sum));
			for (int j = 0; j <10000000; j++)
			{
				sum += (i*j) & 3;
			}
			sum += i & 3;
		}
		KeepLiveEnd
	});
}

 



推荐阅读
  • 如果应用程序经常播放密集、急促而又短暂的音效(如游戏音效)那么使用MediaPlayer显得有些不太适合了。因为MediaPlayer存在如下缺点:1)延时时间较长,且资源占用率高 ... [详细]
  • C++ 异步编程中获取线程执行结果的方法与技巧及其在前端开发中的应用探讨
    本文探讨了C++异步编程中获取线程执行结果的方法与技巧,并深入分析了这些技术在前端开发中的应用。通过对比不同的异步编程模型,本文详细介绍了如何高效地处理多线程任务,确保程序的稳定性和性能。同时,文章还结合实际案例,展示了这些方法在前端异步编程中的具体实现和优化策略。 ... [详细]
  • 为什么多数程序员难以成为架构师?
    探讨80%的程序员为何难以晋升为架构师,涉及技术深度、经验积累和综合能力等方面。本文将详细解析Tomcat的配置和服务组件,帮助读者理解其内部机制。 ... [详细]
  • VB.net 进程通信中FindWindow、FindWindowEX、SendMessage函数的理解
    目录一、代码背景二、主要工具三、函数解析1、FindWindow:2、FindWindowEx:3、SendMessage: ... [详细]
  • 解决Bootstrap DataTable Ajax请求重复问题
    在最近的一个项目中,我们使用了JQuery DataTable进行数据展示,虽然使用起来非常方便,但在测试过程中发现了一个问题:当查询条件改变时,有时查询结果的数据不正确。通过FireBug调试发现,点击搜索按钮时,会发送两次Ajax请求,一次是原条件的请求,一次是新条件的请求。 ... [详细]
  • 在Delphi7下要制作系统托盘,只能制作一个比较简单的系统托盘,因为ShellAPI文件定义的TNotifyIconData结构体是比较早的版本。定义如下:1234 ... [详细]
  • 开机自启动的几种方式
    0x01快速自启动目录快速启动目录自启动方式源于Windows中的一个目录,这个目录一般叫启动或者Startup。位于该目录下的PE文件会在开机后进行自启动 ... [详细]
  • 在《Cocos2d-x学习笔记:基础概念解析与内存管理机制深入探讨》中,详细介绍了Cocos2d-x的基础概念,并深入分析了其内存管理机制。特别是针对Boost库引入的智能指针管理方法进行了详细的讲解,例如在处理鱼的运动过程中,可以通过编写自定义函数来动态计算角度变化,利用CallFunc回调机制实现高效的游戏逻辑控制。此外,文章还探讨了如何通过智能指针优化资源管理和避免内存泄漏,为开发者提供了实用的编程技巧和最佳实践。 ... [详细]
  • 在Cisco IOS XR系统中,存在提供服务的服务器和使用这些服务的客户端。本文深入探讨了进程与线程状态转换机制,分析了其在系统性能优化中的关键作用,并提出了改进措施,以提高系统的响应速度和资源利用率。通过详细研究状态转换的各个环节,本文为开发人员和系统管理员提供了实用的指导,旨在提升整体系统效率和稳定性。 ... [详细]
  • Python 伦理黑客技术:深入探讨后门攻击(第三部分)
    在《Python 伦理黑客技术:深入探讨后门攻击(第三部分)》中,作者详细分析了后门攻击中的Socket问题。由于TCP协议基于流,难以确定消息批次的结束点,这给后门攻击的实现带来了挑战。为了解决这一问题,文章提出了一系列有效的技术方案,包括使用特定的分隔符和长度前缀,以确保数据包的准确传输和解析。这些方法不仅提高了攻击的隐蔽性和可靠性,还为安全研究人员提供了宝贵的参考。 ... [详细]
  • Spring Data JdbcTemplate 入门指南
    本文将介绍如何使用 Spring JdbcTemplate 进行数据库操作,包括查询和插入数据。我们将通过一个学生表的示例来演示具体步骤。 ... [详细]
  • 在Windows系统中安装TensorFlow GPU版的详细指南与常见问题解决
    在Windows系统中安装TensorFlow GPU版是许多深度学习初学者面临的挑战。本文详细介绍了安装过程中的每一个步骤,并针对常见的问题提供了有效的解决方案。通过本文的指导,读者可以顺利地完成安装并避免常见的陷阱。 ... [详细]
  • 为了确保iOS应用能够安全地访问网站数据,本文介绍了如何在Nginx服务器上轻松配置CertBot以实现SSL证书的自动化管理。通过这一过程,可以确保应用始终使用HTTPS协议,从而提升数据传输的安全性和可靠性。文章详细阐述了配置步骤和常见问题的解决方法,帮助读者快速上手并成功部署SSL证书。 ... [详细]
  • 本文详细解析了 Android 系统启动过程中的核心文件 `init.c`,探讨了其在系统初始化阶段的关键作用。通过对 `init.c` 的源代码进行深入分析,揭示了其如何管理进程、解析配置文件以及执行系统启动脚本。此外,文章还介绍了 `init` 进程的生命周期及其与内核的交互方式,为开发者提供了深入了解 Android 启动机制的宝贵资料。 ... [详细]
  • Web开发框架概览:Java与JavaScript技术及框架综述
    Web开发涉及服务器端和客户端的协同工作。在服务器端,Java是一种优秀的编程语言,适用于构建各种功能模块,如通过Servlet实现特定服务。客户端则主要依赖HTML进行内容展示,同时借助JavaScript增强交互性和动态效果。此外,现代Web开发还广泛使用各种框架和库,如Spring Boot、React和Vue.js,以提高开发效率和应用性能。 ... [详细]
author-avatar
小白学习
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有