博客园  :: 首页  :: 新随笔  :: 联系 :: 订阅 订阅  :: 管理

Qt控制流简析

Posted on 2017-05-12 13:41  SolHe  阅读(817)  评论(0编辑  收藏  举报

前言:

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的控制流。希望这篇文章能够帮助相关从业者能更好的设计自己的程序。