pyqt中CPU密集型操作采用子进程+子线程的方式,有效防止界面卡顿

为什么要用子进程

  • 在pyqt中。在做复杂运算的时候、为了让主界面不卡顿。通常我们会启动一个工作线程、用来处理耗时的操作、然后子线程将运行结果通过信号的方式发给主线程、主线程做UI的更新
  • 但是这种方法只适用于I/0密集型、假设工作线程运行的为CPU密集型计算、那么在进程资源直接被占满了。就算用了子线程。UI更新依然会被卡住。
  • 所以针对运算CPU密集型计算。只能是让UI更新在主进程里面运行、并额外创建一个子进程用于计算、进程之间的通信可以考虑使用pipe管道。为了并发处理。考虑在子进程里面用线程池颁发多个线程同时运算
  • 当然针对I/O密集型的操作。不需要用到子进程。用于进程的创建本身就需要耗费一些资源。反而会慢、
  • 下面是demo代码
import concurrent.futures
import multiprocessing
import sys

import loguru
import rawpy
from PIL import Image
from PySide2.QtCore import Slot, Signal, QThread
from PySide2.QtWidgets import QApplication, QMainWindow, QPushButton, QLabel, QWidget, QVBoxLayout


class SubProcess:
    """子进程"""

    def __init__(self, parent_conn):

        self.parent_conn = parent_conn

    def process_files(self, dng_files):
        """在子进程里面启用线程池、提高并发处理能力"""
        with concurrent.futures.ThreadPoolExecutor(max_workers=4) as executor:
            processed_files = []

            def process_and_notify(input_file, output_file):
                """
                处理文件并发送通知
                """
                self.parent_conn.send(["processing", input_file])
                result = self.process_file(input_file, output_file)
                return result

            # 使用map方法并行处理任务
            for input_file, output_file in dng_files.items():
                processed_files.append(executor.submit(process_and_notify, input_file, output_file))

            for future in concurrent.futures.as_completed(processed_files):
                try:
                    status, result = future.result()
                    if status == "success":
                        self.parent_conn.send(["success", result])
                    else:
                        self.parent_conn.send(["fail", result])
                except Exception as e:
                    self.parent_conn.send(["fail", str(e)])
        # 发送关闭管道的信号、关闭管道
        self.parent_conn.send(["ClosePipe", ''])
        self.parent_conn.close()

    def process_file(self, input_file, output_file):
        """
        CPU密集计算逻辑函数
        """
        try:
            with rawpy.imread(input_file) as raw:
                rgb = raw.postprocess(use_camera_wb=True, half_size=True, no_auto_bright=False, output_bps=8)
                image = Image.fromarray(rgb)
                image.save(output_file, quality=85)
                return "success", output_file
        except Exception as e:
            return "fail", str(e)


class Worker(QThread):
    """主进程里的子线程用于和子进程的pipe通信"""
    data_received = Signal(tuple)

    def __init__(self, parent_conn):
        super().__init__()
        self.parent_conn = parent_conn

    def run(self):
        while True:
            if self.parent_conn.poll():
                status, path = self.parent_conn.recv()
                if status == "ClosePipe":
                    break
                self.data_received.emit((status, path))


class MainWindow(QMainWindow):
    """
    主进程
    """
    def __init__(self):
        super().__init__()

        central_widget = QWidget()  # 创建一个中心部件
        self.setCentralWidget(central_widget)  # 将中心部件设置为窗口的中心部件
        layout = QVBoxLayout()  # 创建垂直布局管理器

        self.button = QPushButton("启动子进程")  # 创建按钮
        self.label = QLabel("子进程正在运行")  # 创建标签

        layout.addWidget(self.button)  # 将按钮添加到布局中
        layout.addWidget(self.label)  # 将标签添加到布局中

        central_widget.setLayout(layout)  # 将布局设置为中心部件的布局
        self.button.clicked.connect(self.startSubProcess)

    @Slot()
    def startSubProcess(self):
        # 使用multiprocessing创建两个连接管道,用于子进程和父进程之间的通信
        parent_conn, child_conn = multiprocessing.Pipe()
        # 创建SubProcess对象,传入子进程的连接管道
        sub_process = SubProcess(child_conn)
        # 定义一个包含多个dng文件路径和对应输出文件路径的字典
        dng_files = {
            r"D:\相机样片\尼康人像 - 副本.NEF": r"D:\相机样片\1.jpg",
        }

        # 创建一个multiprocessing.Process对象,指定子进程的目标函数为sub_process.process_files,并传入dng_files作为参数
        self.process = multiprocessing.Process(target=sub_process.process_files, args=(dng_files,))
        # 启动子进程
        self.process.start()

        # 创建Worker对象,传入父进程的连接管道
        self.worker = Worker(parent_conn)
        # 连接Worker的data_received信号,当接收到数据时触发updateUI函数
        self.worker.data_received.connect(self.updateUI)
        # 启动Worker线程
        self.worker.start()
        # 记录日志,表示开始执行
        loguru.logger.info('开始执行')

    @Slot(tuple)
    def updateUI(self, data):
        status, path = data
        self.label.setText(path)
        loguru.logger.info(status)
        loguru.logger.info(path)


if __name__ == "__main__":
    app = QApplication(sys.argv)
    window = MainWindow()
    window.show()
    sys.exit(app.exec_())

  • 21
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值