Qt控制流简析
前言:
Qt库及其绑定python语言的PySide库、PyQt库在圈中已经是TD的标配了,Qt提供了多种快速绘制图形窗口的方式。但正是因为这个原因,导致很多TD局限在设计窗口外观的桎梏中,而忽略了Qt更为本质的控制流。大家都写过类似这样的代码:
# test.py class Window(QtGui.QWidget):
def __init__(self, parent = None):
QtGui.QWidget.__init__(self, parent)
"do something" def func(self,*args,**kwargs):
"do something" if __name__ == '__main__':
app =QtGui.QApplication(sys.argv)win = Window()
win.show() sys.exit(app.exec_())
相信很多人在使用Qt的过程中都会有这样的疑问:
1、为什么要判断__name__ == '__main__';
2、为什么已经通过win.show()显示了自定义的窗口了,还要再添加一句sys.exit(app.exec_());
本文将通过两个简单的代码案例来分析这两个疑问,帮助TD了解Qt的控制流,从而更好的设计自己的程序,让应用更为健壮稳定。
正文:
在解答前言中两个疑问之前,首先看这样一段c++代码,并通过这段代码来解释Qt的控制流:
// test.cpp #include "stdafx.h"
#include <iostream>
#include <conio.h>
#include <windows.h> int main()
{
while (1) { Sleep(1000); char b;
b = _getche();
std::cout << '\n' << b << std::endl; }
return 0;
}
这段代码以main为程序入口向下执行,到while(1)这句时进入一个无穷循环,在这个循环中,首先_getche()会等待并获取console的输入字符,然后再将该字符输出到console中,最后结束本次循环,进入下一次循环。而main函数中最后一句return 0则必须等待while循环中断才能得到执行。
需要值得注意的是,每一次循环main函数作为主线程都会等待console的外界输入,如果console一直没有接受到外界输入,本次循环就会阻塞在这个地方,之后的输出语句也得不到执行。
实际上第二个问题中的app.exec_()就相当于这个while循环,app.exec_()启动了QEventLoop事件循环机制,我们可以将while循环中的外界输入视作Qt中的Signal,将内部输出视作Qt中的Slot,主线程中的QEventLoop循环会一直接受信号输入,然后处理信号(Signal),执行对应的槽函数(Slot)。
app.exec_()不仅启动了QEventLoop事件循环机制,还阻塞了app.exec_()之后的语句的执行,这意味着main函数不会结束,main函数中的局部变量都不会被析构,而我们在main函数中实例化的QWidget对象也不会被析构,会在main函数的生命周期中一直驻留内存,这就意味着QWdiget通过show()函数显示后会一直显示在屏幕中。
当然你也可以尝试去掉app.exec_()语句,再运行一次test.py文件,这时你会发现你的QWidget会在屏幕上一闪而过,这是因为程序没有了app.exec_()的阻塞,会非常快速的执行完毕,这意味着main函数的生命周期会很短,main函数中的局部变量都会被快速的析构,所以你的窗口会一闪而过。
看完上面的分析希望你已经初步了解了app.exec_()在Qt控制流中的作用,接下来会进一步分析如何优化Qt的控制流。
我们看到test.cpp中有一句Sleep(1000),这会导致当前循环会在此处等待1秒钟,如果等待的是1分钟会有什么后果呢?这会意味着while循环会有一分钟的时间无法处理外界输入和内部输出。在Qt中则意味着主线程会等待1分钟,在这段时间中信号得不到响应,槽函数不会被触发,语句之前显示的QWidget会得不到刷新,这就是Qt程序运行时遇到的假死情况。这个情况发生的根本原因在于有一段耗时较长的业务代码执行时阻塞了QWidget的刷新,如果要完整执行完这段业务代码,QWidget就会有对应的一段时间得不到响应。这实际上就是单一线程的性能瓶颈,单一主线程无法保证QWidget得到流畅响应的同时还能处理多个高耗时的计算任务。app.exec_()
想要解决这个单一线程的性能瓶颈问题,就需要将耗时间较长的业务代码放到另外一个线程中去执行,这样主线程就不会被阻塞,QWidget就会一直处于流畅响应的状态。
在Qt中实际上已经提供了QThread类来解决这个问题。QThread与python自带threading.Thread接口设计非常相似,你需要添加一个类继承QThread,并重新实现run()方法,将高耗时的业务代码放到run方法中,再通过QPushButton一类的控件来触发执行这样一段伪代码:
def newThread(self):
thread = SubQThread()
thread.start()
start()方法将会调用run方法在一个新的线程中来执行你的高耗时代码块。注意,新的线程是从属于主线程的,新的线程实际上主线程的子线程,在子线程中是不允许创建QWidget实例的,因为父子线程共享同一块内存空间,在子线程中创建QWidget实例会在内存逻辑中产生冲突,Qt也不允许这样做。所以推荐在主线程中创建QWidget实例,再通过信号槽机制来控制QWidget状态的更新。
现在回到前言中第一个疑问,相信大家已经明白了,__name__ == '__main__'就是判断当前线程是否是主线程,如果不是主线程,就不会创建QWidget实例并显示,也不会执行app.exec_()所包含的一系列行为,通过这种方式就避免了非主线程执行这些代码导致的内存逻辑错误。
以上就是Qt的一个基本的控制流。
结语:
写程序的本质其实就是给出一个控制流,告诉计算机应该按照怎样的行为步骤来达到我们想要的结果,本文通过两个简单的例子试图以小见大的展现Qt的控制流。希望这篇文章能够帮助相关从业者能更好的设计自己的程序。
Qt控制流简析的更多相关文章
- 简析平衡树(四)——FHQ Treap
前言 好久没码过平衡树了! 这次在闪指导的指导下学会了\(FHQ\ Treap\),一方面是因为听说它可以可持久化,另一方面则是因为听说它是真的好写. 简介 \(FHQ\ Treap\),又称作非旋\ ...
- 简析.NET Core 以及与 .NET Framework的关系
简析.NET Core 以及与 .NET Framework的关系 一 .NET 的 Framework 们 二 .NET Core的到来 1. Runtime 2. Unified BCL 3. W ...
- 简析 .NET Core 构成体系
简析 .NET Core 构成体系 Roslyn 编译器 RyuJIT 编译器 CoreCLR & CoreRT CoreFX(.NET Core Libraries) .NET Core 代 ...
- RecycleView + CardView 控件简析
今天使用了V7包加入的RecycleView 和 CardView,写篇简析. 先上效果图: 原理图: 这是RecycleView的工作原理: 1.LayoutManager用来处理RecycleVi ...
- Java Android 注解(Annotation) 及几个常用开源项目注解原理简析
不少开源库(ButterKnife.Retrofit.ActiveAndroid等等)都用到了注解的方式来简化代码提高开发效率. 本文简单介绍下 Annotation 示例.概念及作用.分类.自定义. ...
- PHP的错误报错级别设置原理简析
原理简析 摘录php.ini文件的默认配置(php5.4): ; Common Values: ; E_ALL (Show all errors, warnings and notices inclu ...
- Android 启动过程简析
首先我们先来看android构架图: android系统是构建在linux系统上面的. 所以android设备启动经历3个过程. Boot Loader,Linux Kernel & Andr ...
- Android RecycleView + CardView 控件简析
今天使用了V7包加入的RecycleView 和 CardView,写篇简析. 先上效果图: 原理图: 这是RecycleView的工作原理: 1.LayoutManager用来处理RecycleVi ...
- Java Annotation 及几个常用开源项目注解原理简析
PDF 版: Java Annotation.pdf, PPT 版:Java Annotation.pptx, Keynote 版:Java Annotation.key 一.Annotation 示 ...
随机推荐
- SQLServer数据库
分离数据库:右键数据库→任务→分离数据库→确定 附加数据库:数据库右键→任务→附加→选择要附加的dlf文件→附加 导出SQL脚本步骤:右键数据库→任务→生成脚本→高级→要编写脚本的数据的类型→架构和数 ...
- IDEAL 热更新
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring- ...
- mysql_pconnect 问题
不同于mysql_connect的短连接,mysql_pconnect持久连接的时候,将先尝试寻找一个在同一个主机上用同样的用户名和密码已经打开的(持久)连接,如果找到,则返回此连接标识而不打开新连接 ...
- WCF分布式4:客户端访问寄宿在IIS中的WCF服务
部署过程比较简单,新建一个站点,指向服务的物理路径,设置一个端口.即可. 新建的站点对应一个应用程序池,设置应用程序池中的.NET版本为4.0 写一个测试客户端,访问IIS中的WCF服务,可能会出现, ...
- jdbc工具类的封装,以及表单验证数据提交后台
在之前已经写过了jdbc的工具类,不过最近学习了新的方法,所以在这里重新写一遍,为后面的javaEE做铺垫: 首先我们要了解javaEE项目中,文件构成,新建一个javaEE项目,在项目中,有一个we ...
- 删除csdn上面自己上传的资源
今天想删掉以前的资源,才发现CSDN并没有提供删除资源的功能,然后去网上搜了下,这才删除了,不知道怎么删除的小伙伴看过来~ 首先,找到自己要删除资源的页面,举个栗子 https://download. ...
- python vtk 通过回调函数监测键盘”Up”键动作,每按一次方向上键,actor变换一种颜色
import vtk class KeyPressInteractorStyle(vtk.vtkInteractorStyleTrackballCamera): def __init__(self,p ...
- Vue中添加新的路由并访问
1.搭建好Vue脚手架(这里使用的版本是Vue2.0) 2.在代码编辑器(这里使用的是Sublime Text)打开项目文件夹 3.在文件目录src中的component下创建一个新的vue页面,写入 ...
- JavaScript中为什么使用立即执行函数来封装模块?
最近在学习JavaScript基础,在学习到面向对象编程时,学习到在JavaScript中实现模块化的方法,其中一个重要的点是如何封装私有变量. 实现封装私有变量的方法主要是: 使用构造函数 func ...
- Python mysql-python及pycurl使用一例
#环境:CentOS Linux release 7.5.1804 (Core) mini安装,使用python2.7 #使用pucurl对输入的url地址进行测试,将结果存放到mysql中,代码来之 ...