PyWebView打包exe后多进程启动MCP服务器导致程序无限重启的解决方法
PyWebView打包exe后多进程启动MCP服务器导致程序无限重启的解决方法
我来帮你搞定这个无限重启的问题——这其实是PyInstaller打包多进程Python程序在Windows环境下的典型坑,根源在于子进程的创建逻辑触发了主程序UI的重复启动。下面一步步给你讲清楚原因和解决办法:
问题根源
在Windows系统中,PyInstaller打包后的exe使用multiprocessing时,默认采用spawn方式创建子进程。这种方式会重新加载并执行整个脚本文件,如果没有正确的入口保护,子进程会再次运行main()函数,从而重复创建PyWebView窗口,形成无限循环。
具体解决步骤
1. 添加多进程打包支持(最关键)
在你的main()函数开头或者入口代码块中,必须调用multiprocessing.freeze_support()——这是PyInstaller专门为打包多进程程序提供的修复,它会告诉子进程跳过主程序入口的执行逻辑,只运行指定的target函数。
2. 替换不可靠的__del__清理逻辑
Python的__del__方法执行时机不确定,程序退出时可能不会被及时调用,导致MCP进程无法正常终止。建议改用PyWebView的窗口关闭事件来处理进程清理,更可靠。
3. 调整PyInstaller打包命令
确保打包时包含所有依赖的资源文件(比如UI的HTML、图标),避免运行时找不到文件。
修改后的完整代码示例
调整PyWebView主程序部分
import multiprocessing import webview import logging # 导入你的MCP相关代码 from your_mcp_file import FastMCP class CommandsAutomatorApi: def init_mcp(self, mcp_runner): self.mcp_process = multiprocessing.Process(target=mcp_runner.run_mcp) self.mcp_process.start() class MCPRunner: def run_mcp(self): # 子进程内单独初始化MCP对象,避免全局实例导致的重复加载问题 mcp = FastMCP("resume_fetcher") @mcp.tool() async def get_files(): return 'using tool' # 配置并启动MCP服务器 mcp.settings.mount_path = "/mcp" mcp.settings.port = 8765 mcp.settings.host = "127.0.0.1" logging.debug(f"Starting MCP server at http://{mcp.settings.host}:{mcp.settings.port}{mcp.settings.mount_path}") mcp.run(transport="streamable-http") def main(): # 必须先调用freeze_support,处理PyInstaller打包后的多进程问题 multiprocessing.freeze_support() api = CommandsAutomatorApi() mcp_runner = MCPRunner() api.init_mcp(mcp_runner) # 定义窗口关闭时的清理逻辑,比__del__更可靠 def on_window_closed(window): if hasattr(api, 'mcp_process') and api.mcp_process.is_alive(): api.mcp_process.terminate() api.mcp_process.join() logging.info("MCP server process terminated successfully") # 创建WebView窗口并绑定关闭事件 window = webview.create_window( 'Commands Automator', 'ui/commands_automator.html', js_api=api, width=1000, height=800, icon='ui/resources/Commands_Automator.ico' ) window.events.closed += on_window_closed webview.start() if __name__ == '__main__': main()
关键修改说明
- MCP对象移到子进程内初始化:原来的全局MCP实例会在子进程启动时被重复加载,移到
run_mcp方法内可以避免这个问题,确保每个子进程有独立的MCP实例。 - 添加窗口关闭清理逻辑:替代不可靠的
__del__方法,确保UI关闭时MCP进程能被及时终止。
正确的PyInstaller打包命令
假设你的主脚本名为app.py,资源都在ui文件夹下,使用以下命令打包:
pyinstaller --onefile --windowed --add-data "ui;ui" --name CommandsAutomator app.py
--onefile:打包成单个exe文件--windowed:不显示控制台窗口(如果不需要调试可以添加)--add-data "ui;ui":将本地的ui文件夹打包到exe中,运行时会自动解压到临时目录,保持路径一致性
为什么这些修改有效
freeze_support()会在子进程启动时拦截入口代码的执行,只运行multiprocessing.Process指定的run_mcp目标函数,不会重复启动UI窗口。- 子进程内单独初始化MCP对象,避免了全局变量导致的资源冲突和重复加载问题。
- 窗口关闭事件的清理逻辑能确保MCP进程在UI关闭时被及时终止,避免残留后台进程。
内容来源于stack exchange
相关产品推荐
相关产品推荐

