You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何从进程池向PySide6 Qt GUI实时发送消息更新进度

问题根源

你的代码核心问题在于:点击按钮后主线程直接调用了do_stuff(),而multiprocessing.Pool.map()是阻塞方法,会一直等待所有子进程任务完成才返回。这导致主线程被彻底卡住,无法及时处理UI更新信号,队列中的消息只能在所有任务结束后一次性被批量处理。

解决方案

将进程池的执行逻辑移到独立的后台QThread中,让主线程专注处理UI事件;同时优化进度更新的显示方式,确保实时性。

修改后的完整代码

模块文件(other_module.py)

如果是实际FTP下载,可在foo中替换为真实的下载逻辑并实时计算进度:

import os
import multiprocessing
import time

shared_queue = multiprocessing.Queue()

class MyProcess:
    def foo(self, file_info):
        # 模拟FTP下载任务,实际场景替换为真实下载逻辑
        file_name, total_size = file_info
        pid = os.getpid()
        downloaded = 0
        
        # 实时上报下载进度
        while downloaded < total_size:
            downloaded += 1024 * 100  # 模拟每次下载100KB
            progress = (downloaded / total_size) * 100
            output = f"进程 {pid} | {file_name}: {progress:.1f}%"
            shared_queue.put(output)
            time.sleep(0.1)  # 模拟网络延迟
        
        # 上报下载完成状态
        output = f"进程 {pid} | {file_name} 下载完成"
        shared_queue.put(output)
        print(output)

    def do_stuff(self):
        pool_size = 4
        # 模拟4个待下载文件,实际场景替换为FTP服务器文件列表
        files = [
            ("file1.txt", 1024*1024),
            ("file2.txt", 1024*1024),
            ("file3.txt", 1024*1024),
            ("file4.txt", 1024*1024)
        ]
        p = multiprocessing.Pool(pool_size)
        p.map(self.foo, files)

主模块文件

import sys
from PySide6.QtWidgets import QApplication, QWidget, QLabel, QPushButton, QVBoxLayout
from PySide6.QtCore import QThread, Signal

from other_module import MyProcess, shared_queue 

class QueueListenerThread(QThread):
    """监听进程间队列,将进度信号传递给主线程"""
    update_signal = Signal(str)

    def run(self):
        while True:
            try:
                text = shared_queue.get(block=True)
                self.update_signal.emit(text)
            except Exception:
                # 处理队列异常(如队列被关闭)
                break

class ProcessPoolThread(QThread):
    """在后台线程执行进程池任务,避免阻塞主线程"""
    def run(self):
        task = MyProcess()
        task.do_stuff()

class Window(QWidget):
    def __init__(self):
        super().__init__()
        self.label = QLabel("等待任务开始...")
        self.button = QPushButton("启动下载")

        # 布局设置
        layout = QVBoxLayout()
        layout.addWidget(self.label)
        layout.addWidget(self.button)
        self.setLayout(layout)

        # 初始化并启动队列监听线程
        self.queue_thread = QueueListenerThread()
        self.queue_thread.update_signal.connect(self.update_label)
        self.queue_thread.start()

        # 初始化进程池线程
        self.pool_thread = ProcessPoolThread()
        self.pool_thread.finished.connect(lambda: self.button.setEnabled(True))

        # 绑定按钮点击事件
        self.button.clicked.connect(self.start_task)

    def update_label(self, text):
        """实时更新UI显示,累加进度信息"""
        current_text = self.label.text()
        new_text = f"{current_text}\n{text}" if current_text != "等待任务开始..." else text
        self.label.setText(new_text)

    def start_task(self):
        """启动下载任务,防止重复点击"""
        if not self.pool_thread.isRunning():
            self.label.setText("任务启动中...")
            self.button.setEnabled(False)
            self.pool_thread.start()

if __name__ == "__main__":
    app = QApplication([])
    window = Window()
    window.show()
    sys.exit(app.exec())

关键修改点说明

  1. 新增ProcessPoolThread:把进程池的执行逻辑移到后台线程,彻底避免主线程被阻塞,确保UI能实时响应信号。
  2. 队列监听线程独立:保持QueueListenerThread专注于监听进程间队列,将进度信号传递给主线程处理。
  3. UI更新优化:改为累加显示进度信息,方便查看所有文件的下载状态;移除不必要的QApplication.processEvents(),主线程会自动处理UI更新。
  4. 按钮状态控制:任务启动后禁用按钮,任务结束后恢复,防止重复触发任务。

内容的提问来源于stack exchange,提问作者Jan Donal

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.12 19:20:01