使用cx_Freeze打包后Python subprocess执行超时卡住问题排查
以下是导致cx_Freeze打包后应用卡住的几个核心原因,以及对应的修复方案:
1. shell=True导致PID指向错误进程
当设置shell=True时,subprocess.Popen实际启动的是系统cmd.exe进程,而非目标的STM32_Programmer_CLI.exe。此时process.pid是cmd.exe的PID,调用os.kill仅终止shell进程,真正的CLI工具可能仍在后台运行,导致主进程的while循环一直检测到子进程未结束,永久卡住。
解决办法:
去掉shell=True参数,直接启动目标程序,确保process.pid对应正确进程:
process = subprocess.Popen( [ 'STM32_Programmer_CLI.exe', '-c', 'port=SWD', 'freq=4000', ], text=True, cwd=self.cubeProgPath, stdout=subprocess.PIPE )
注意:若STM32_Programmer_CLI.exe不在系统PATH中,需传入绝对路径,或确保cwd指向工具所在的正确目录。
2. 未读取标准输出导致管道阻塞
代码中设置了stdout=subprocess.PIPE但未读取输出,当子进程输出填满管道缓冲区时,子进程会被挂起,无法退出,进而导致主进程的while循环一直等待,形成死锁。脚本运行时可能因终端缓冲机制或输出量小未触发,但打包成独立exe后更容易出现。
解决办法:
- 若不需要输出,直接丢弃:
stdout=subprocess.DEVNULL - 若需要输出,在循环中定期读取缓冲区:
while True: # 读取一行输出,避免缓冲区阻塞 line = process.stdout.readline() if line: # 按需处理输出内容 pass if process.poll() is not None: res = True break elif (time.time() - stamp) > myTimeout: process.terminate() break
3. CTRL_C_EVENT信号无法正确生效
Windows系统中,signal.CTRL_C_EVENT需要发送到控制台进程组,cx_Freeze打包后的exe可能无法将信号正确传递给子进程(尤其是shell=True时,信号仅发给shell进程,不会转发给子进程)。超时后子进程无法终止,主进程持续卡在循环中。
解决办法:
使用process.terminate()或process.kill()替代os.kill,这两个方法更可靠,直接终止子进程:
# 超时终止时替换为 process.terminate() # 优雅终止,等价于TerminateProcess # 或强制终止 # process.kill()
4. 工作路径cwd在打包后失效
打包后的exe运行时,self.cubeProgPath指向的路径可能与脚本运行时不同(比如exe所在目录变化),导致子进程无法找到STM32_Programmer_CLI.exe,但shell=True会让shell进程仅报错而不退出,主进程循环一直检测到进程未结束。
解决办法:
动态获取打包后的exe所在目录,拼接工具路径:
import sys import os # 获取当前exe所在目录 exe_dir = os.path.dirname(sys.executable) # 假设工具与exe在同一目录 tool_path = os.path.join(exe_dir, 'STM32_Programmer_CLI.exe') process = subprocess.Popen( [tool_path, '-c', 'port=SWD', 'freq=4000'], text=True, stdout=subprocess.PIPE )
额外代码问题
你的异常捕获语法错误,应写成except Exception as e:而非except e:,这会导致脚本遇到异常时无法正确处理,打包后可能引发未预期错误。
内容的提问来源于stack exchange,提问作者WITC

