PyInstaller打包PySide6程序为EXE时出现运行冻结、分页文件过小错误及多进程异常问题求助
搞定PyInstaller打包PySide6应用后的冻结、分页文件告警和多进程问题
兄弟,你遇到的这个问题我之前帮好几个朋友排查过,咱们一步步拆解来解决:
先明确你的核心症状
- 本地跑
python test.py完全正常,GUI和功能都没问题 - 打包成EXE后一启动就卡死,电脑甚至跟着冻住,还弹“分页文件过小”的提示,任务管理器里能看到一堆同程序的进程
问题根源分析
从你的代码和表现来看,主要是三个地方出了问题:
1. 线程逻辑完全搞错了,耗时操作堵死主线程
看你MainWindow里的线程代码:
self.thread = Thread() self.thread_init = Thread() self.thread.finished.connect(lambda: self.ui.run_button_2)
这绑定的槽函数根本没实际调用,而且你在run()方法里是先同步执行完耗时的bt.RunBacktest(),再启动线程——这完全是本末倒置!回测计算本来就耗资源,你把它放在UI主线程里执行,直接导致GUI卡死,系统资源占满,最后触发分页文件告警。用户一看程序没反应,反复点EXE,自然就多了一堆进程。
2. 漏了os导入,打包后找不到资源文件
你获取application_path的代码里没导入os模块,打包后程序找不到csv资源,可能会反复尝试读取,进一步加重资源占用。
3. PyInstaller打包配置没跟上Qt的需求
PySide6的一些组件(比如QtCharts)需要手动指定隐藏导入,资源文件也得明确告诉打包程序要包含进去,否则容易出各种诡异问题。
一步步解决
第一步:把耗时操作挪到线程里,解放UI主线程
咱们重写线程类,用信号传递结果,不让回测堵死UI:
# 先加个信号导入 from PySide6.QtCore import Signal class BacktestThread(QThread): # 定义信号,用来传递回测结果 backtest_done = Signal(object, object, object, object, object, object, object, object, object) def __init__(self, config, df): super().__init__() self.config = config self.df = df.copy() def run(self): # 把耗时的回测放到线程里跑 days, Profit, MarketProfit, accumProfit, accumMarketProfit, alpha, Volatility, CARG, SharpRatio = bt.RunBacktest( self.config, self.df) # 跑完发信号给主线程处理结果 self.backtest_done.emit(days, Profit, MarketProfit, accumProfit, accumMarketProfit, alpha, Volatility, CARG, SharpRatio) # 然后修改MainWindow的run方法和添加结果处理 def run(self): # 防止重复点击启动多个线程 if not hasattr(self, 'backtest_thread') or not self.backtest_thread.isRunning(): self.backtest_thread = BacktestThread(self.config, self.df) self.backtest_thread.backtest_done.connect(self.handle_backtest_result) self.backtest_thread.start() # 暂时禁用按钮,避免重复触发 self.ui.run_button_2.setEnabled(False) def handle_backtest_result(self, days, Profit, MarketProfit, accumProfit, accumMarketProfit, alpha, Volatility, CARG, SharpRatio): # 主线程里更新UI self.create_profit_table( days, Profit, MarketProfit, alpha, accumProfit, accumMarketProfit) # 恢复按钮可用 self.ui.run_button_2.setEnabled(True)
第二步:补上缺失的os导入
在代码最顶部加上:
import os
不然打包后os.path相关的代码全报错,程序肯定乱套。
第三步:优化PyInstaller的spec配置
先生成spec文件:
pyi-makespec test.py
然后打开生成的test.spec,修改成这样:
a = Analysis( ['test.py'], pathex=[], binaries=[], # 把你的csv资源文件加进去,路径根据实际调整,比如src下的所有csv datas=[('src/*.csv', 'src')], # 手动指定PySide6.QtCharts和numpy为隐藏导入,避免打包漏了 hiddenimports=['PySide6.QtCharts', 'numpy'], hookspath=[], hooksconfig={}, runtime_hooks=[], excludes=[], win_no_prefer_redirects=False, win_private_assemblies=False, cipher=None, noarchive=False, ) pyz = PYZ(a.pure, a.zipped_data, cipher=None) exe = EXE( pyz, a.scripts, a.binaries, a.zipfiles, a.datas, [], name='YourAppName', # 改成你的程序名 debug=False, bootloader_ignore_signals=False, strip=False, upx=True, upx_exclude=[], runtime_tmpdir=None, console=False, # 不需要控制台的话设为False,减少额外窗口 disable_windowed_traceback=False, argv_emulation=False, target_arch=None, codesign_identity=None, entitlements_file=None, )
然后用spec打包:
pyinstaller test.spec
第四步:加个单实例检查,防止多进程
可以在主入口加个检查,避免用户反复点EXE开多个进程:
from PySide6.QtWidgets import QMessageBox def check_single_instance(): from PySide6.QtCore import QSharedMemory # 随便整个唯一的键,比如你的程序名 app_key = "MyBacktestApp_12345" shared_mem = QSharedMemory(app_key) if not shared_mem.create(1): QMessageBox.warning(None, "提示", "程序已经在运行啦!") return False return True if __name__ == '__main__': if not check_single_instance(): sys.exit(0) app = QApplication(sys.argv) window = MainWindow(app) window.show() sys.exit(app.exec())
最后验证
- 先跑修改后的
python test.py,确认回测时GUI不会卡死,按钮能正常禁用/恢复 - 用修改后的spec重新打包
- 启动EXE,看任务管理器只有一个进程,GUI能正常启动,回测时也不会冻住
内容的提问来源于stack exchange,提问作者jone lee
相关产品推荐
相关产品推荐

