当前位置: 代码网 > it编程>前端脚本>Python > Python webview打包版卡死、开发版正常的解决方法

Python webview打包版卡死、开发版正常的解决方法

2026年09月09日 Python 我要评论
现象:源码/测试版(python -m src.main)语音播报流程流畅;pyinstaller 发行包卡在「播报文稿生成 ~38%」,进度与超时均失效。结论:不是业务算法差异,而是冻结进程内引擎的

现象:源码/测试版(python -m src.main)语音播报流程流畅;pyinstaller 发行包卡在「播报文稿生成 ~38%」,进度与超时均失效。

结论:不是业务算法差异,而是冻结进程内引擎的启动方式触发了 windows 默认 proactoreventloop + 日志锁竞争,并叠加 gui 进程代理继承等问题。

1. 现象对照

维度开发 / 测试版打包发行版(修复前)
启动入口python -m src.main桌面客户端程序.exesys.frozen=true
播报引擎进程模型独立子进程engine/.venv/.../python -m uvicorn app.main:app同进程守护线程:桌面壳内 threading.thread + uvicorn
事件循环子进程默认策略,外网断开路径相对独立线程内 asyncio.run → 默认 proactoreventloop
典型卡住点任务中心显示「播报文稿生成 · …」约 38%,可挂死十余分钟
超时是否生效正常不生效(阶段 420s 超时、httpx 超时也无法打断)
日志engine/engine.out.logengine-data/engine.log;挂死时常停在某条 [vo.normalize] / 大模型调用前后

前端 38% 来自播报阶段序号映射:script_storyboard 在有效步骤序列中约对应 3/8,即「播报文稿 agent 生成中」阶段,不是业务写死的 38 这个魔法数

2. 架构分叉(问题入口)

桌面通过 produceengine.ensure_started() 拉起引擎,分支如下:

ensure_started()
├── frozen == false  →  子进程 uvicorn(开发路径)
└── frozen == true   →  _ensure_started_inprocess()(打包路径)

对应代码:src/produce_engine.py

选择进程内线程的初衷合理:发行包无 .venv,依赖已打进 _internal,避免再附带解释器。副作用是:引擎与无控制台 gui、webview2、同步日志写文件共享同一进程地址空间,异步运行时行为与「独立 uvicorn」不再等价。

3. 问题定位过程

3.1 业务侧排除

  1. 同一数据库/缓存服务、大模型密钥配置下,开发版可完成同类型播报,说明 大模型、对象存储、库表本身可用
  2. 卡住任务 db 状态长期为 runningprogress_message 停在「播报文稿 agent 生成中…」,completed_at 为空。
  3. 阶段超时常量 stage_timeout_script_storyboard = 420 存在,但挂死后 远超 420s 仍不失败 → 更像是 事件循环本身不再调度,而非单纯「模型慢」。

3.2 卡住热路径

播报文稿阶段主链:

pipeline._run_script_storyboard
  → agent_plan.generate_script_and_storyboard
      → search(httpx)
      → llm_client.chat_json(httpx)
      → finalize_voiceover_storyboard_sync(to_thread)
  → 进度心跳 _with_progress_heartbeat

日志中常见停顿点:[vo.script] step=llm_generate / [vo.normalize] dedupe begin 等。停在日志语句附近,提示 写日志与 asyncio 回调交叉,而不一定是 normalize 算法复杂度。

3.3 uvicorn 在 windows 上的关键行为

uvicorn.server.run() 大致为:

config.setup_event_loop()
asyncio.run(self.serve(...))

uvicorn.loops.asyncio.asyncio_setup 源码(uvicorn 0.34):

def asyncio_setup(use_subprocess: bool = false) -> none:
    if sys.platform == "win32" and use_subprocess:
        asyncio.set_event_loop_policy(asyncio.windowsselectoreventlooppolicy())

config.use_subprocess 仅在 reloadworkers > 1 时为真。
桌面进程内单 worker、无 reload:setup_event_loop() 等于空操作。

python 3.12 on windows 默认策略 → asyncio.run() 创建 proactoreventloop

开发版:独立 python -m uvicorn 子进程,即使也是 proactor,也 不与 gui 主线程、同一套 rotatingfilehandler 高频争用;连接关闭异常路径的危害面更小,表现为「测试 ok」。

3.4 修复后的验证证据

新包启动后 engine-data/engine.log 出现:

冻结引擎 asyncio.run loop_factory -> _windowsselectoreventloop
引擎事件循环: _windowsselectoreventloop (selector=selectselector frozen=true)

说明发行路径已强制 selector;此前仅 set_event_loop_policy 再调用 server.run(),仍可能被 asyncio.run() 按默认工厂建出 proactor。

4. 根本原因

4.1 主因:proactor + 冻结 gui 线程内引擎 → 异步死锁式挂起

因果链:

  1. 打包版引擎跑在桌面进程的守护线程里。
  2. uvicorn 未因 use_subprocess 切换 selector,asyncio.run 使用 proactoreventloop
  3. 播报阶段大量短连接(大模型对话、搜索、对象存储 head/get 等);远端关闭连接时,proactor 管道回调 _call_connection_lost 可能在回调里抛出 oserror(如 winerror 10038)。
  4. asyncio 默认异常处理器走 logging;同时业务 worker / 心跳线程高频写 engine.log(文件 handler)。
  5. logging handler 锁与异常再入写日志叠加,可导致 业务协程永久卡在 logging 调用上;事件循环无法继续调度 → httpx 超时、asyncio.wait_for 超时全部失效
  6. ui 仍按 stage 显示「播报文稿生成 ~38%」,表现为「打包就卡、测试不卡」。

这与「算法在打包后变慢」无关,是 运行时模型差异

4.2 次因:gui 进程继承系统代理

无控制台 pyinstaller 进程常带上用户/机器级 http(s)_proxy
httpx 默认 trust_env=true 时会走异常代理,外网调用表现为无限挂起,同样容易停在播报文稿阶段。

开发子进程环境变量集合往往不同,故不易复现。
对策(已落地):httpx_client_kwargs = {trust_env: false, proxy: none},并在冻结引擎线程入口清理代理环境变量、设置 no_proxy=*

4.3 伴随问题(放大「卡」的体感,但非 38% 主因)

问题表现说明
删除项目未释放内存队列新任务显示「排队等待(第 2 位)」只删 db 行,未 cancel 同类型 asyncio.lock 等待者;计数泄漏
发行包漏打 aiomysql「启动播报引擎失败: no module named ‘aiomysql’」壳与引擎共用冻结解释器,根 requirements.txt 未对齐引擎依赖
httpx 未进 collect_all偶发导入/运行异常清单已补齐

5. 修复方案(已实现)

5.1 强制 selector 事件循环(主修复)

_ensure_started_inprocess不再依赖 server.run() / uvicorn 的 setup_event_loop,改为:

  1. 设置 windowsselectoreventlooppolicy
  2. asyncio.run(serve(), loop_factory=policy.new_event_loop),保证真正创建的是 selector 循环;
  3. lifespan 日志打印 frozen 与 loop 类型;若仍为 proactor 则打 error,便于回归。

5.2 代理隔离

  • 全局 httpx:trust_env=false + proxy=none
  • 冻结线程:剥离 http_proxy / https_proxy / all_proxy

5.3 删除与队列

  • delete_series 前调用 task_queue.abandon_series_tasks,取消内存 worker,避免假排队。

5.4 打包依赖

  • requirements.txt 对齐引擎(含 aiomysqlhttpx 等);
  • package_manifest.py 将相关包列入 collect_all / required_internal

6. 为何「测试 ok、打包就卡」可一句话概括

测试版引擎是独立进程;打包版引擎挤进 gui 同进程线程,且误用 windows proactor 事件循环。连接关闭与日志争用把 asyncio 卡死,于是播报文稿阶段永远停在约 38%,超时也救不回来。

次要因素(代理、假排队、缺依赖)会叠加失败模式,但与「流畅 vs 卡死」二分最对齐的,是 进程模型 + 事件循环类型 这一条。

7. 回归检查清单

打包后启动,确认:

  1. dist/.../engine-data/engine.log_windowsselectoreventloopfrozen=true
  2. 执行播报任务后,progress_message 能在数分钟内越过「播报文稿 agent 生成中」,或在超时后变为失败而非永久 running。
  3. 删除进行中项目后,新建任务不应无故「第 2 位」排队。
  4. verify_package.py 通过,且 _internalaiomysqlhttpx

8. 相关代码与文档索引

路径作用
src/produce_engine.pyfrozen 分支、selector loop_factory、清代理、安全日志 handler
engine/app/main.py启动时打印事件循环类型;proactor 告警
engine/app/services/volc/http_utils.pyhttpx 禁用环境代理
engine/app/services/task_queue.py同类型串行锁、abandon_series_tasks
engine/app/services/pipeline.pydelete_series 先放弃队列再删库
scripts/package_manifest.py / requirements.txt发行依赖对齐
docs/packaging-notes.md打包结构与分发注意

9. 后续建议(可选)

  1. 长期:评估打包版也改为「附带嵌入式 python / sidecar uvicorn」,与开发模型一致,降低同进程耦合。
  2. ci:冻结冒烟用例——启动 exe 后读 engine.log,断言 selector + /api/health
  3. 可观测性:卡住时在任务中心展示「无心跳超过 n 分钟」并允许一键失败重置(前端已部分具备「无活跃任务则推进失败」逻辑)。

以上就是python webview打包版卡死、开发版正常的解决方法的详细内容,更多关于python webview打包版卡死开发版正常的资料请关注代码网其它相关文章!

(0)

相关文章:

版权声明:本文内容由互联网用户贡献,该文观点仅代表作者本人。本站仅提供信息存储服务,不拥有所有权,不承担相关法律责任。 如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 2386932994@qq.com 举报,一经查实将立刻删除。

发表评论

验证码:
Copyright © 2017-2026  代码网 保留所有权利. 粤ICP备2024248653号
站长QQ:2386932994 | 联系邮箱:2386932994@qq.com