背景
基于Qt的应用程序(像大多数GUI应用程序一样)是基于事件的。这意味着执行是根据用户交互、信号和计时器来驱动的。在事件驱动的应用程序中,单击按钮将创建一个事件,应用程序随后将处理该事件以产生预期的输出。事件被推入和从事件队列取出,然后按顺序处理。
事件循环通过在QApplication对象上调用.exec_()开始,并在与Python代码相同的线程中运行。运行这个事件循环的线程——通常称为GUI线程——也处理与主机操作系统的所有窗口通信。
默认情况下,由事件循环触发的任何执行也将在这个线程中同步运行。在实践中,这意味着当PyQt应用程序在代码中执行某些操作时,窗口通信和GUI交互将被阻塞。
如果您所做的很简单,并且快速地将控制返回到GUI循环,那么用户将无法察觉这种阻塞。但是,如果您需要执行长时间运行的任务,例如打开/写入一个大文件、下载一些数据或渲染一些复杂的界面,那么就会出现问题。对用户来说,应用程序似乎没有响应(因为它确实没有响应)。因为你的应用程序不再与操作系统通信,在MacOS X上,如果你点击你的应用程序,你会看到旋转的死亡轮。没有人希望这样。
解决方案很简单:将您的工作从GUI线程中取出(放到另一个线程中)。PyQt(通过Qt)提供了一个简单的接口来实现这一点。
线程和进程
在PyQt应用程序中运行独立任务有两种主要方法:线程和进程。
线程共享相同的内存空间,因此启动速度快,消耗的资源最少。共享内存使得在线程之间传递数据变得非常简单,但是从不同的线程读/写内存可能会导致竞争条件或分段错误。在Python中还有一个额外的问题,即多个线程被同一个全局解释器锁(GIL)绑定——这意味着非GIL释放的Python代码一次只能在一个线程中执行。但是,这不是PyQt的主要问题,因为大部分时间都花在Python之外。
进程使用独立的内存空间(和一个完全独立的Python解释器)。这样可以避免GIL的任何潜在问题,但代价是启动时间较慢、内存开销较大以及发送/接收数据的复杂性。
为了简单起见,这篇文章将使用线程演示;
QRunnable和QThreadPool
Qt为在线程中运行提供了非常简单的接口。它是围绕两个类构建的:QRunnable和QThreadPool。前者是您想要执行的工作的容器,而后者是您将该工作传递给其他线程的方法。from PyQt5.QtGui import *
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *