前言:

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分钟,在这段时间中信号得不到响应,槽函数不会被触发,app.exec_()语句之前显示的QWidget会得不到刷新,这就是Qt程序运行时遇到的假死情况。这个情况发生的根本原因在于有一段耗时较长的业务代码执行时阻塞了QWidget的刷新,如果要完整执行完这段业务代码,QWidget就会有对应的一段时间得不到响应。这实际上就是单一线程的性能瓶颈,单一主线程无法保证QWidget得到流畅响应的同时还能处理多个高耗时的计算任务。

想要解决这个单一线程的性能瓶颈问题,就需要将耗时间较长的业务代码放到另外一个线程中去执行,这样主线程就不会被阻塞,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控制流简析的更多相关文章

  1. 简析平衡树(四)——FHQ Treap

    前言 好久没码过平衡树了! 这次在闪指导的指导下学会了\(FHQ\ Treap\),一方面是因为听说它可以可持久化,另一方面则是因为听说它是真的好写. 简介 \(FHQ\ Treap\),又称作非旋\ ...

  2. 简析.NET Core 以及与 .NET Framework的关系

    简析.NET Core 以及与 .NET Framework的关系 一 .NET 的 Framework 们 二 .NET Core的到来 1. Runtime 2. Unified BCL 3. W ...

  3. 简析 .NET Core 构成体系

    简析 .NET Core 构成体系 Roslyn 编译器 RyuJIT 编译器 CoreCLR & CoreRT CoreFX(.NET Core Libraries) .NET Core 代 ...

  4. RecycleView + CardView 控件简析

    今天使用了V7包加入的RecycleView 和 CardView,写篇简析. 先上效果图: 原理图: 这是RecycleView的工作原理: 1.LayoutManager用来处理RecycleVi ...

  5. Java Android 注解(Annotation) 及几个常用开源项目注解原理简析

    不少开源库(ButterKnife.Retrofit.ActiveAndroid等等)都用到了注解的方式来简化代码提高开发效率. 本文简单介绍下 Annotation 示例.概念及作用.分类.自定义. ...

  6. PHP的错误报错级别设置原理简析

    原理简析 摘录php.ini文件的默认配置(php5.4): ; Common Values: ; E_ALL (Show all errors, warnings and notices inclu ...

  7. Android 启动过程简析

    首先我们先来看android构架图: android系统是构建在linux系统上面的. 所以android设备启动经历3个过程. Boot Loader,Linux Kernel & Andr ...

  8. Android RecycleView + CardView 控件简析

    今天使用了V7包加入的RecycleView 和 CardView,写篇简析. 先上效果图: 原理图: 这是RecycleView的工作原理: 1.LayoutManager用来处理RecycleVi ...

  9. Java Annotation 及几个常用开源项目注解原理简析

    PDF 版: Java Annotation.pdf, PPT 版:Java Annotation.pptx, Keynote 版:Java Annotation.key 一.Annotation 示 ...

随机推荐

  1. springboot +redis配置

    springboot +redis配置 pom依赖 <dependency> <groupId>org.springframework.boot</groupId> ...

  2. Maven可以使用mvn package指令对项目进行打包,如果使用Java -jar xxx.java

    Maven可以使用mvn package指令对项目进行打包,如果使用Java -jar xxx.jar执行运行jar文件,会出现"no main manifest attribute, in ...

  3. c++ — 运算符重载与strcmp自实现

    #include <iostream> #include <string.h> #include <stdlib.h> using namespace std; c ...

  4. ListView嵌套ScrollView会出现的问题

    1.出现的问题:ListView高度显示不对,滑动冲突 (1)解决方法:如果说listview的数据一屏就可以显示完整,那么只需要在xml或者代码里给listview设置固定高度即可. 如果listv ...

  5. java.io.IOException: 你的主机中的软件中止了一个已建立的连接。

    1.异常表现:我在jsp文件中有一个<form>表单,里面有一个<button>保存事件按钮.<button  onclick="addOrUPdate()&q ...

  6. 【算法导论】【排序】—— 计数排序(counting sort)

    计数排序的特点: 需要额外的数组以存储: 中间过程数据(记为数组 C),数组 C 的下标是待排序序列的元素值,下标对应的值为出现的次数: 排序后的序列(记为 B),计数排序仅获取原始待排序序列的值,对 ...

  7. FlappyBirdWeb素材资源

    https://pan.baidu.com/s/1UeNmhnmK4RInmfiEduPrAQ 先上网盘地址 https://github.com/JIANGYUJING1995/FlappyBird ...

  8. hyperscan应用参数

    >>hs_compile_ext_multi 使用额外的参数编译表达式, 额外的参数包括: MIN_OFFSET 距离开始的最小偏移开始匹配 MAX_OFFSET 距离开始的最大偏移结束匹 ...

  9. JavaScript原型链及继承

    在JavaScript中,所有的东西都是对象,但是JavaScript中的面向对象并不是面向类,而是面向原型的,这是与C++.Java等面向对象语言的区别,比较容易混淆,因此把我自己学习的过程记录下来 ...

  10. java的基础语法(标识符 修饰符 关键字)

    Java 基础语法 一个 Java 程序可以认为是一系列对象的集合,而这些对象通过调用彼此的方法来协同工作.下面简要介绍下类.对象.方法和实例变量的概念. 对象:对象是类的一个实例,有状态和行为.例如 ...