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()
关键修改说明
- 异步启动子进程:用
QProcess.start()替代subprocess.Popen(),不会阻塞主UI线程,主窗口可以正常响应操作。 - 信号驱动的输出捕获:通过
readyReadStandardOutput和readyReadStandardError信号实时捕获子进程的输出,不用等待进程结束。 - 进程结束后的日志处理:绑定
finished信号,在子进程完全退出后根据退出码判断是否写入错误日志,避免遗漏输出。
测试方法
把三个文件放在同一目录,运行MainWindow.py:
- 点击Run Tool 01:主窗口不会冻结,子进程启动失败后,错误日志会写入同目录的
log.txt。 - 点击Run Tool 02:主窗口正常响应,子进程成功启动并显示窗口,同时主窗口可以继续操作。
内容的提问来源于stack exchange,提问作者JokerMartini
相关产品推荐
相关产品推荐

