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

Python Popen启动子进程:不阻塞主窗口且写入错误日志

解决子进程启动阻塞主窗口+错误日志写入问题

你的问题核心在于同步等待子进程完成——原代码里的subprocess.Popen().communicate()会让主UI线程一直等待子进程结束,期间没法处理界面刷新、用户操作,所以主窗口就冻结了。咱们用Qt原生的QProcess来替代subprocess,它是异步的,既能不卡主窗口,又能完美捕获输出并写入日志。

问题根源拆解

原代码中execute_tool方法里的process.communicate()是同步调用,会阻塞主线程直到子进程退出。这就导致主窗口在子进程运行期间完全无法响应,出现“冻结”现象。

解决方案:用QProcess实现异步子进程管理

下面是修改后的完整代码,只需要修改MainWindow.py,tool_01.py和tool_02.py保持不变即可:

修改后的MainWindow.py

import sys
import os
from PySide import QtGui, QtCore

class MainWindow(QtGui.QDialog):
    def __init__(self, parent=None):
        super(MainWindow, self).__init__(parent)
        self.resize(300, 200)
        
        # 按钮初始化
        self.ui_Run01 = QtGui.QPushButton("Run Tool 01 (CREATES ERROR)")
        self.ui_Run01.clicked.connect(lambda: self.run_tool(os.path.abspath('tool_01.py')))
        
        self.ui_Run02 = QtGui.QPushButton("Run Tool 02 (WORKING)")
        self.ui_Run02.clicked.connect(lambda: self.run_tool(os.path.abspath('tool_02.py')))
        
        # 布局
        layout = QtGui.QVBoxLayout()
        layout.addWidget(self.ui_Run01)
        layout.addWidget(self.ui_Run02)
        self.setLayout(layout)

    def run_tool(self, filepath):
        # 创建QProcess实例,每个子进程用独立的QProcess
        self.process = QtCore.QProcess(self)
        exe = sys.executable
        
        # 设置进程启动参数
        self.process.start(exe, [filepath])
        
        # 绑定信号:捕获标准输出和错误输出
        self.process.readyReadStandardOutput.connect(self.on_process_output)
        self.process.readyReadStandardError.connect(self.on_process_error)
        
        # 绑定信号:进程结束后处理退出码和日志
        self.process.finished.connect(lambda exit_code: self.on_process_finished(exit_code, filepath))

    def on_process_output(self):
        # 捕获标准输出(可选,这里打印到控制台)
        output = self.process.readAllStandardOutput().data().decode()
        print(f"Output: {output}")

    def on_process_error(self):
        # 捕获错误输出(可选,这里打印到控制台)
        error = self.process.readAllStandardError().data().decode()
        print(f"Error: {error}")

    def on_process_finished(self, exit_code, filepath):
        # 进程结束后,判断是否需要写入错误日志
        if exit_code != 0:
            # 获取完整的输出(包括标准输出和错误输出)
            full_output = self.process.readAllStandardOutput().data().decode() + self.process.readAllStandardError().data().decode()
            
            # 写入日志文件
            logfile = os.path.abspath('log.txt')
            directory = os.path.dirname(logfile)
            if not os.path.exists(directory):
                os.makedirs(directory)
            
            with open(logfile, 'w') as f:
                f.write(full_output)
            
            print(f"错误日志已写入: {logfile}")
        else:
            print(f"工具 {os.path.basename(filepath)} 运行成功")

def main():
    app = QtGui.QApplication(sys.argv)
    ex = MainWindow()
    ex.show()
    sys.exit(app.exec_())

if __name__ == '__main__':
    main()

保持不变的tool_01.py

import sys
import os
import fake #CAUSE ERROR LOG WRITE
from PySide import QtGui, QtCore

class ToolWindow(QtGui.QMainWindow):
    def __init__(self, parent=None):
        super(ToolWindow, self).__init__(parent)
        self.resize(250, 100)
        self.ui_Run = QtGui.QLabel("Second Tool")
        self.setCentralWidget(self.ui_Run)

def main():
    app = QtGui.QApplication(sys.argv)
    ex = ToolWindow()
    ex.show()
    sys.exit(app.exec_())

if __name__ == '__main__':
    main()

保持不变的tool_02.py

import sys
import os
from PySide import QtGui, QtCore

class ToolWindow(QtGui.QMainWindow):
    def __init__(self, parent=None):
        super(ToolWindow, self).__init__(parent)
        self.resize(250, 100)
        self.ui_Run = QtGui.QLabel("Working Tool")
        self.setCentralWidget(self.ui_Run)

def main():
    app = QtGui.QApplication(sys.argv)
    ex = ToolWindow()
    ex.show()
    sys.exit(app.exec_())

if __name__ == '__main__':
    main()

关键修改说明

  1. 异步启动子进程:用QProcess.start()替代subprocess.Popen(),不会阻塞主UI线程,主窗口可以正常响应操作。
  2. 信号驱动的输出捕获:通过readyReadStandardOutput和readyReadStandardError信号实时捕获子进程的输出,不用等待进程结束。
  3. 进程结束后的日志处理:绑定finished信号,在子进程完全退出后根据退出码判断是否写入错误日志,避免遗漏输出。

测试方法

把三个文件放在同一目录,运行MainWindow.py:

  • 点击Run Tool 01:主窗口不会冻结,子进程启动失败后,错误日志会写入同目录的log.txt。
  • 点击Run Tool 02:主窗口正常响应,子进程成功启动并显示窗口,同时主窗口可以继续操作。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.15 03:30:32