121 lines
6.1 KiB
Markdown
121 lines
6.1 KiB
Markdown
---
|
||
id: T-604
|
||
title: AI工场拉取主图 QThread 生命周期闪退修复
|
||
phase: 7
|
||
deps: [T-591]
|
||
status: TODO
|
||
created: 2026-07-11
|
||
---
|
||
|
||
## 问题 / 背景
|
||
|
||
运行最新代码,在⑥AI工场输入商品ID后点击「拉取主图」,程序可能闪退,控制台报错:
|
||
|
||
```text
|
||
QThread: Destroyed while thread is still running
|
||
```
|
||
|
||
初步分析确认这不是 Shopee/CDP 读取主图本身的错误,而是 AI工场 tab 的后台线程生命周期管理不一致导致:
|
||
|
||
- `app/gui/tabs/image_studio.py` 的 `_start_worker()` 只用 `self._running_thread` 保存当前 `QThread`。
|
||
- `ImageStudioPullImagesWorker` 返回结果或失败后,`_on_pull_finished()` / `_on_worker_failed()` 会调用 `_finish_worker()`。
|
||
- `_finish_worker()` 立刻把 `self._running_thread = None`。
|
||
- 此时 `worker.finished` 虽然已经发出,但 `QThread` 可能还没有真正发出 `thread.finished`、事件循环还没退出。
|
||
- Python 引用被提前清掉后,Qt 线程对象可能在仍运行时被销毁,从而触发闪退。
|
||
|
||
对比①采集、②AI生成、③更新、⑤设置等模块,它们都是把线程引用保留到 `thread.finished` 后再清理。⑥AI工场当前没有按同样模式处理,所以「拉取主图」「下载原图」「生成图片」「继续查询」「导出终选」这些共用 `_start_worker()` 的操作理论上都有同类风险,只是拉取主图在失败/快速返回时更容易触发。
|
||
|
||
还有一个交互缺口:`pull_main_images()` 如果 `current_project` 不为空,会忽略用户刚在输入框里填的新商品ID,继续使用当前项目的旧 `account_alias/item_id`。这会造成“输入商品ID拉取主图”行为不符合用户预期。
|
||
|
||
## 方案
|
||
|
||
### 1. 修复 AI工场 QThread 生命周期
|
||
|
||
修改 `app/gui/tabs/image_studio.py`:
|
||
|
||
- `_start_worker(worker, thread_name)` 改成与其他 tab 一致的模式:
|
||
- 调用 `run_worker(worker, thread_name=thread_name, start=False)`。
|
||
- 连接 `thread.finished` 到新的 `_forget_running_thread(thread)`。
|
||
- 先保存 `self._running_worker = worker`、`self._running_thread = thread`。
|
||
- 设置运行态后再 `thread.start()`。
|
||
- 新增 `_forget_running_thread(thread)`:
|
||
- 只有 `self._running_thread is thread` 时,才把 `self._running_thread = None`、`self._running_worker = None`。
|
||
- 只在 `thread.finished` 后清理线程引用。
|
||
- `_finish_worker()` 不再直接清空 `self._running_thread`。
|
||
- 它只负责恢复按钮/控件运行态。
|
||
- 如需避免重复恢复 UI,可以增加 `_worker_running` 或通过当前线程引用判断,保持幂等。
|
||
|
||
### 2. 避免失败路径重复处理
|
||
|
||
当前 `BaseWorker.run()` 异常时会先发 `failed`,再发 `finished({"ok": False})`。AI工场的各个 finished 槽又会在 `ok=False` 时转调 `_on_worker_failed()`,可能导致同一个失败重复弹窗/重复清理。
|
||
|
||
本任务同步收敛 AI工场失败处理:
|
||
|
||
- 对 `worker.failed` 仍保留 `_on_worker_failed()`,显示中文错误和恢复 UI。
|
||
- 对各 `_on_*_finished(summary)` 中 `summary.get("ok") is False` 的分支,避免再次弹窗;可以直接返回,或只在当前仍处于 running 且未处理失败时处理一次。
|
||
- 不改 `BaseWorker` 的全局行为,避免影响其他 tab。
|
||
|
||
### 3. 拉取主图尊重当前输入
|
||
|
||
修改 `pull_main_images()`:
|
||
|
||
- 点击「拉取主图」时,以当前账号下拉和商品ID输入框为准。
|
||
- 如果当前输入与 `current_project` 不一致,允许创建/切换到对应项目后再拉取。
|
||
- 不再因为 `current_project is not None` 就强制覆盖成旧项目的 `account_alias/item_id`。
|
||
- 保留输入为空、账号为空时的中文阻断提示。
|
||
|
||
### 4. 不改 CDP 读取主图业务链路
|
||
|
||
本任务不修改:
|
||
|
||
- `app/image_studio.py::pull_remote_main_image_urls()` 的业务语义。
|
||
- `app/editor.py` 的 `open_product()`、`read_product_image_urls()`、`close_readonly_product()`。
|
||
- 账号登录检测、Chrome 启动、Shopee 页面选择器。
|
||
|
||
如果拉取失败,仍应通过现有 worker 错误路径给中文弹窗和日志,不应闪退。
|
||
|
||
## 验收要点
|
||
|
||
- 在⑥AI工场输入商品ID,点击「拉取主图」,成功时不闪退,主图列表刷新。
|
||
- 商品ID无效、账号未登录、Chrome/CDP不可用、Shopee页面加载失败等快速失败场景下,不再出现 `QThread: Destroyed while thread is still running`。
|
||
- 「拉取主图」失败只弹一次错误提示,不重复弹窗、不重复写同一条失败日志。
|
||
- 拉取主图运行期间按钮禁用;完成或失败后按钮恢复。
|
||
- 连续多次拉取不同商品ID时,使用当前输入框里的商品ID,不被旧 `current_project` 覆盖。
|
||
- 「下载原图」「开始生成」「继续查询任务」「导出终选」这些共用 AI工场 `_start_worker()` 的操作不回归,完成/失败后线程引用都在 `thread.finished` 后清理。
|
||
- 不改变 CDP 读取蝦皮主图逻辑,不改变商品详情页打开/关闭规则。
|
||
|
||
## 测试要求
|
||
|
||
优先补 GUI 单元测试,覆盖线程生命周期和拉取参数:
|
||
|
||
- `tests/test_gui.py` 或新增合适测试文件:
|
||
- mock `run_worker()` 返回可控 fake thread,确认 `_start_worker()` 先保存线程,只有触发 `thread.finished` 后才清空引用。
|
||
- 调用 `_finish_worker()` 后确认不会提前清空 `self._running_thread`。
|
||
- 构造 `current_project` 与输入框商品ID不一致的场景,确认 `ImageStudioPullImagesWorker` 使用当前输入的商品ID。
|
||
|
||
验证命令:
|
||
|
||
```bash
|
||
python -m ruff check app tests main.py
|
||
py -3.10 -m compileall app main.py
|
||
py -3.10 -m unittest discover -s tests
|
||
git diff --check
|
||
```
|
||
|
||
如能人工验证,补充运行 GUI 后的实测:
|
||
|
||
- 输入一个有效商品ID拉取主图成功。
|
||
- 输入一个无效商品ID拉取主图失败但不闪退。
|
||
|
||
## 边界(不改什么)
|
||
|
||
- 不改 `app/workers.py` 的全局 `BaseWorker.run()` 和 `run_worker()` 行为,避免影响①②③⑤。
|
||
- 不改 Shopee/CDP 选择器和读取主图逻辑。
|
||
- 不改 AI工场 DB schema。
|
||
- 不改 cmhub 生图、下载、导出业务规则。
|
||
- 不引入新的多线程框架。
|
||
|
||
## 执行记录
|
||
|
||
待执行。
|