refactor: 复用持久设备工作线程 (#110)

This commit is contained in:
chengma
2026-08-10 18:00:38 +08:00
parent 553659208a
commit 70cedc34d4
11 changed files with 563 additions and 149 deletions
+18 -6
View File
@@ -156,11 +156,10 @@ Qt 主线程
├── 窗口、页面、表格模型和用户事件
└── 接收后台信号并更新界面
任务工作线程
├── Admin 请求
├── uiautomator2 调用
持久设备工作线程(单设备、固定 QThread)
├── uiautomator2 Device 的创建、健康检查、调用和释放
├── 页面等待与 XML 解析
└── 单个任务的串行执行
└── 采集、采购、订单核对和手动重新执行的串行命令
结果提交工作线程或同一任务线程的独立队列
└── Outbox 重试,不重复执行 PDD 操作
@@ -172,6 +171,12 @@ Qt 主线程
- `[必须]` QWidget 只能在 Qt 主线程创建和访问。
- `[必须]` 一个 Android 设备由一个工作线程独占,不跨线程共享 uiautomator2 Device 对象。
- `[必须]` 同一设备在 90 秒空闲期内只复用 Device 连接;每个任务必须重新读取
当前应用、页面、控件树、商品、价格、规格和采购安全状态。
- `[必须]` 停止自动获取、切换或删除已保存设备、设备断连、配置变化、窗口关闭
和空闲超时都会在设备线程释放连接。关闭只使用协作停止和 `quit()/wait()`。
- `[必须]` `task_runs.irreversible_action_at` 有值后只允许把核单命令放入队列,
不得重新执行采购下单步骤。
- `[必须]` 后台信号只传递不可变数据、稳定编号或轻量视图模型。
- `[必须]` 点“停止获取”后不再领取新任务,当前任务在定义的安全点退出。
- `[建议]` 关闭窗口时应选择停止、等待或后台继续;MVP 默认安全停止并持久化状态。
@@ -259,6 +264,12 @@ def start_task(self, remote_task_id: str) -> None:
| 用 `thread.terminate()` 停任务 | 数据库写一半、订单状态不明 | 用 `cancel()` 置标志位,在安全点退出 |
| 在 `run()` 里改界面 | 随机崩溃,且很难复现 | 只 emit 信号 |
PDD 设备执行器是这个模板的长生命周期版本:QThread 在第一次设备任务时创建,
后续通过队列信号提交一条命令,命令完成后线程继续等待;不能在 Worker 内写无限
轮询。设备 Worker 仍不访问界面。停止接单后等待当前命令到安全点结束,再在线程内
释放 Device,最后执行 `quit()/wait()`。Outbox 批量重新上报不操作手机,继续使用
独立的网络 Worker,不进入设备命令队列。
### 5.2 后台结果回主线程 / 迟到结果
窗口关掉了,后台任务还在跑,跑完再发信号——这时槽函数去访问已经销毁的控件,程序就崩了。
@@ -403,8 +414,9 @@ reconcile_purchase(task, run) -> PurchaseResult | ManualReview
或电话。最终匹配由领域服务完成,不能让页面解析层单独决定采购成功。
正式 Client 由 `pdd_u2_purchase_adapter.py` 分别实现演练和 live 接口,并在
`ui_main.py` 注入工厂。Adapter 通过 `PddDeviceService` 独占连接,每次判断
都重新读取当前包名和控件树。当前 Admin 下发的 `color` 和 `size`
`ui_main.py` 注入工厂。Adapter 通过设备工作线程绑定的
`PersistentPddDeviceService` 独占一次任务会话;同一设备可在 90 秒内复用底层
Device 连接,但每次判断都重新读取当前包名和控件树。当前 Admin 下发的 `color` 和 `size`
使用精确文字匹配;其他动态维度直接停止,不做相似匹配。原生 PDD
控件树不暴露商品编号,因此商品编号来自 Adapter 本次已校验并打开的
PDD URL,包名和页面类型仍以最新控件树确认。