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

如何在PyQt5 QListWidget中实时显示subprocess.Popen输出?

解决PyQt5 QListWidget实时显示subprocess.Popen输出的问题

原代码核心问题

  • 主线程阻塞:backup函数在UI主线程中执行,包含time.sleep和子进程循环,导致UI无法实时刷新,所有列表项需等函数执行完毕才会显示。
  • 变量名错误:循环中误写self.info.addItem,实际列表控件是self.list,会触发异常。
  • 子进程输出缓冲:默认输出缓冲机制导致readline无法实时获取子进程输出。
  • 同步sleep阻塞UI:结尾的time.sleep会让UI完全卡死,无法更新倒计时内容。

修正后的代码

import subprocess
from PyQt5.QtWidgets import (QApplication, QListWidgetItem, QListWidget, 
                             QWidget, QPushButton, QGridLayout, QMainWindow)
from PyQt5.QtCore import QThread, pyqtSignal, QTimer


class BackupThread(QThread):
    # 定义信号,用于向主线程传递输出内容
    output_signal = pyqtSignal(str)
    finished_signal = pyqtSignal()

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

    def run(self):
        try:
            # 设置行缓冲,确保实时读取输出
            with subprocess.Popen(
                self.command,
                stdout=subprocess.PIPE,
                stderr=subprocess.STDOUT,
                encoding='utf-8',
                bufsize=1,
                universal_newlines=True
            ) as result:
                for line in result.stdout:
                    if line.strip():
                        self.output_signal.emit(line.strip())
            self.finished_signal.emit()
        except Exception as excp:
            self.output_signal.emit(f"执行出错: {str(excp)}")
            self.finished_signal.emit()


class MyMainWindow(QMainWindow):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)

        self.setWindowTitle('PopenTest')
        self.cw = QWidget()
        self.list = QListWidget(self)
        self.button = QPushButton("Backup!")
        self.layout = QGridLayout()
        self.layout.addWidget(self.list)
        self.layout.addWidget(self.button)
        self.cw.setLayout(self.layout)
        self.setCentralWidget(self.cw)

        self.button.clicked.connect(self.start_backup)
        self.countdown_timer = QTimer()
        self.countdown_timer.timeout.connect(self.update_countdown)
        self.countdown = 3

        self.show()

        # 替换为实际的mariabackup命令
        # self.backup_command = [r"C:\Program Files\MariaDB 11.4\bin\mariabackup.exe", "--backup"]
        self.backup_command = [r"C:\Program Files\Notepad++\notepad++.exe", "-loadingTime"]

    def start_backup(self):
        self.button.setEnabled(False)
        self.list.clear()
        self.list.addItem(QListWidgetItem("开始执行备份..."))
        
        # 创建并启动后台线程
        self.backup_thread = BackupThread(self.backup_command)
        self.backup_thread.output_signal.connect(self.add_list_item)
        self.backup_thread.finished_signal.connect(self.on_backup_finished)
        self.backup_thread.start()

    def add_list_item(self, text):
        # 主线程中更新列表,确保UI实时刷新
        self.list.addItem(QListWidgetItem(text))
        # 自动滚动到最新条目
        self.list.scrollToBottom()

    def on_backup_finished(self):
        self.countdown = 3
        self.list.addItem(QListWidgetItem(f"窗口将在{self.countdown}秒后关闭..."))
        self.countdown_timer.start(1000)

    def update_countdown(self):
        self.countdown -= 1
        if self.countdown > 0:
            self.list.addItem(QListWidgetItem(f"窗口将在{self.countdown}秒后关闭..."))
        else:
            self.countdown_timer.stop()
            self.close()


if __name__ == "__main__":
    import sys
    app = QApplication(sys.argv)
    mainW = MyMainWindow()
    sys.exit(app.exec_())

关键改进点说明

  • 线程分离:将子进程执行逻辑放到QThread后台线程,避免阻塞UI主线程,确保列表实时更新。
  • 信号传递:通过pyqtSignal在后台线程和主线程间传递输出内容,符合PyQt线程安全机制。
  • 输出缓冲处理:设置bufsize=1和universal_newlines=True,强制子进程使用行缓冲,保证实时获取输出。
  • 非阻塞倒计时:用QTimer替代time.sleep,实现无阻塞的倒计时更新,UI始终响应。

内容的提问来源于stack exchange,提问作者Stefan Bongers

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.22 07:15:22