本文目录
PyQt5桌面应用系列
- PyQt5桌面应用开发(0/22):总结再出发
- PyQt5桌面应用开发(1):需求分析
- PyQt5桌面应用开发(2):事件循环
- PyQt5桌面应用开发(3):并行设计
- PyQt5桌面应用开发(4):界面设计
- PyQt5桌面应用开发(5):对话框
- PyQt5桌面应用开发(6):文件对话框
- PyQt5桌面应用开发(7):文本编辑+语法高亮与行号
- PyQt5桌面应用开发(8):从QInputDialog转进到函数参数传递
- PyQt5桌面应用开发(9):经典布局QMainWindow
- PyQt5桌面应用开发(10):界面布局基本支持
- PyQt5桌面应用开发(11):摸鱼也要讲基本法,两个字,16
- PyQt5桌面应用开发(12):QFile与线程安全
- PyQt5桌面应用开发(13):QGraphicsView框架
- PyQt5桌面应用开发(14):数据库+ModelView+QCharts
- PyQt5桌面应用开发(15):界面动画
- PyQt5桌面应用开发(16):定制化控件-QPainter绘图
- PyQt5桌面应用开发(17):类结构+QWebEngineView
- PyQt5桌面应用开发(18):自定义控件界面设计与实现
- PyQt5桌面应用开发(19):事件过滤器
- PyQt5桌面应用开发(20):界面设计结果自动测试(一)
- PyQt5桌面应用开发(21):界面设计结果自动测试(二)
前言
对一个桌面应用程序来说,有一个无法回避的主题就是并行设计。其主要的原因就是在计算机读取数据或者运行耗时计算时,用户界面必须避免“卡死”的状态。粗略分析,有两种情况。第一种情况是跟IO相关的操作。第二种情况是要消耗大量CPU计算的操作。
并行运行需求:IO vs. CPU
IO密集型
IO密集型的情况下,程序花事先无法确定的时间等待IO返回的数据。对于网络通信、文件接收、传感器的数据采集,底层采取定时或者是其他连接方式,最终数据是通过数据集合(文件块、数据包)来得到的。
在这种情况下,即使Python里面具有GIL的限制,多线程也能够非常有效地解决。如果是大批量数据通道,还有一些基于协程的方式。
在PyQt5中,可以通过线程池来解决。事先准备一个线程池,启动IO密集型的线程,通过信号与槽来处理交互。
大概来说:调用线程池和IO线程的主程序,只需要处理:
- 错误
- 完成
- 数据
- 进度
这么几个信号就能够完成任务。
如果需要在IO的同时实时来把数据更新在界面上,那么对进度信号略做改变就能够完成。
CPU密集型
计算或者是CPU密集型的任务,由于Python的限制(GIL),采用线程没有一点的帮助。
那么就应该采取进程的方法,并通过管道来传递数据。这里就有一个设计选择。可以通过PyQt5的进程,也可以通过Python的进程。
这里直接说结论:
- PyQt5的进程需要自己处理Python运算代码的传输、数据传输管道,相当复杂;
- Python提供了良好的进程并行支持,但是不支持传输PyQt5中QObject的子类。
因此,我这里提供一个思路。具体计算(Python代码的函数)的实现放到一个Python提供的进程中完成,返回数据通过队列自动处理的管道完成,这就把CPU计算封装为一个IO(对PyQt5主程序而言)。
与前面的IO密集型任务一样,采用线程池和Qt线程完成。
并行运行设计
PyQt的核心并行机制:QThreadPool与QRunnable
核心的对象和关系如图,主程序启动QThreadPool,里面运行的都是QRunnable的子类,称为Worker。这里的Worker分为进程和线程两类。进程类封装一个Python进程。两个都通过信号与槽跟PyQt5主程序交互。

QThreadPool的方法如下图,通过start函数来启动QRunnable对象。还可以管理最大线程数。

QRunnable的run函数就是线程执行的主要操作。这里设计的两个子类,构造函数都是一个函数对象和这个函数的参数、命名参数。

而交互的信号也可以专门定义一个类WorkerSignals.

CPU密集型进程
对于CPU密集型的进程,专门设计一个WorkerProcess类,来进行同样的封装,为一个值得注意的是,创建这个类的QProcessWorker必须维持一个队列,也就是下图中构造函数中的第三个参数:multiple_process_queue。

完成设计之后,就可以将前面的采集音频的程序做亿点点改造。可以选择用进程也可以选择用线程。二者都能够保证采集和播放的过程大体不影响界面的响应(操作图片、变更频域和时域)。值得注意的是,进程的方式,启动时间会大大高于线程。
代码
PyQt5桌面应用并行库
这部分代码用户无需修改,定义了信号类(必须是QObject的子类)、进程类、线程任务、进程任务。
import multiprocessing as mp
import sys
from PyQt5.QtCore import QObject, pyqtSignal, QRunnable, pyqtSlot
class WorkerSignals(QObject):
"""
Defines the signals available from a running worker thread.
Supported signals are:
finished
No data
error
`tuple` (exctype, value, traceback.format_exc() )
result
`object` data returned from processing, anything
progress
`int` indicating % progress
"""
finished = pyqtSignal()
error = pyqtSignal(tuple)
result = pyqtSignal(object)
progress = pyqtSignal(int)
class QtThreadWorker(QRunnable):
"""
Worker thread
Inherits from QRunnable to handler worker thread setup, signals and wrap-up.
:param callback: The function callback to run on this worker thread. Supplied args and
kwargs will be passed through to the runner.
:type callback: function
:param args: Arguments to pass to the callback function
:param kwargs: Keywords to pass to the callback function
"""
def __init__(self, fn, *args, **kwargs):
super(QtThreadWorker, self).__init__()
# Store constructor arguments (re-used for processing)
self.fn = fn
self.args = args
self.kwargs = kwargs
self.signals: WorkerSignals = WorkerSignals()
@pyqtSlot()
def run(self):
"""
Initialise the runner function with passed args, kwargs.
"""
# Retrieve args/kwargs here; and fire processing using them
try:
result = self.fn(*self.args, **self.kwargs)
except:
import traceback
traceback.print_exc()
exctype, value = sys.exc_info()[:2]
self.signals.error

本文详细介绍了如何在PyQt5桌面应用中处理并行运行的需求,特别是针对IO密集型和CPU密集型任务。使用QThreadPool和QRunnable可以有效地进行线程管理和任务执行。对于IO密集型任务,多线程能有效提升性能;而对于CPU密集型任务,推荐使用进程以绕过GIL限制。文章还提供了一个并行库的实现示例,展示了如何在实际应用中结合线程池和进程来处理不同类型的并行任务。
最低0.47元/天 解锁文章
3007

被折叠的 条评论
为什么被折叠?



