如何在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
相关产品推荐
相关产品推荐

