11 KiB
AppState 与事件流问题审计
审计日期:2026-08-03
审计范围:硬件事件、AppStateService、调试面板、探测器帧队列、BGA 向导及窗口生命周期。
本文最初用于记录代码审阅结论;2026-08-04 更新:第一、二阶段(P0-1、P0-2、P1-1、P1-2)已修复并通过编译验证,其余问题仍待处理,详见第 4、5 节。
1. 审计结论
整体架构判断成立:硬件层通过 Prism 事件进入 AppStateService,再由状态事件通知 ViewModel。当前发现的问题主要集中在事件去重、资源生命周期和未完成的回退路径,不属于架构需要整体重写的问题。
审阅材料中的结论需要按下面的状态理解:
| 编号 | 问题 | 当前判断 | 证据 |
|---|---|---|---|
| P0-1 | BGA 检测方法存在无条件 return,流水线结果缺失时静默退出 |
已修复(2026-08-04) | 删除 InspectionTaskWizardViewModel.cs 中的无条件 return,流水线复用失败时回退到真实检测路径(原有仿真数据兜底不变);CS0162 不可达代码警告已消除 |
| P0-2 | MotionState 事件可能无变化洪泛 |
已修复(2026-08-04) | AppStateService.cs:790-795 SetMotionState 去重改为值比较(old == newState,record 自动生成 Equals),设备静止时不再无条件触发 UI 更新 |
| P1-1 | _acquireQueue 长期保留大尺寸帧 |
已修复(2026-08-04) | DetectorFramePipelineService.cs 移除 _acquireQueue,改为原子饱和计数器,不再持有约 37MB/帧的图像对象;AcquireQueueCount/AcquireQueueCapacity 对外行为不变,property 测试无需改动 |
| P1-2 | 调试面板 PerformanceMonitorViewModel 事件订阅无法解除 |
已修复(2026-08-04) | PerformanceMonitorViewModel.cs 匿名 lambda 改为具名事件处理器,Dispose 中完整反订阅 7 个 AppState 事件 |
| P1-3 | _latestGeometry 跨线程读写缺少明确同步 |
确认存在,风险待量化 | AppStateService.cs:161、AppStateService.cs:566-571 |
| P1-4 | 探测器断连状态判断不是单一原子事务 | 部分确认 | AppStateService.cs:579-585 在后台线程读取 _detectorState;最终状态更新仍通过原子替换,但 wasConnected 与后续更新之间可能交错 |
| P1-5 | CncExecutionStateChanged 没有主项目订阅方 |
确认存在 | AppStateService 有发布事件,但当前主项目未发现 += 订阅;部分界面改读 MainViewportService.IsCncRunning,形成双来源风险 |
| P1-6 | 硬件错误事件没有进入统一 SystemState |
确认存在 | MotionErrorEvent、探测器 ErrorOccurredEvent、射线源 ErrorOccurredEvent 在硬件层有发布,但主项目未发现对应统一订阅;射线源自身 ViewModel 的局部订阅不等于全局故障状态闭环 |
| P1-7 | OperationMode 始终为 Idle,权限守卫可能失效 |
确认存在 | 当前唯一写入点 RecipeService.cs:225 仍写入 OperationMode.Idle;PermissionService.cs:159 依赖该字段 |
| P2-1 | 标定矩阵/画面联动没有外部调用方 | 基本确认 | UpdateCalibrationMatrix、RequestLinkedView、UpdateLinkedViewState 的调用主要位于 AppStateService 自身,未发现业务入口;需结合实际 UI 需求决定是接入还是删除 |
| P2-2 | WindowLauncherService.ShowOrActivateVisible 关闭后保留窗口引用 |
确认存在 | WindowLauncherService.cs:91-106 没有像 ShowOrActivate 一样挂载 Closed 清理逻辑;接口注释也明确记录了该历史行为 |
| P2-3 | 事件投递顺序与最新属性值可能不一致 | 待验证 | 目前代码确实存在 Interlocked.Exchange 与 Dispatcher.BeginInvoke 的组合,但是否造成实际错序需要并发测试或日志证据,不能仅凭静态审阅定性为已发生故障 |
| P2-4 | CncRoiEditRequestedPayload 携带 Action 委托 |
设计风险确认 | CncEvents.cs:54-60 将回调放入全局事件 Payload,存在生命周期、重入和异常传播风险 |
| P2-5 | 探测器每帧复制大数组并被多处缓存 | 部分确认 | DetectorFramePipelineService.cs:99-114 每帧复制原始数组并生成 BitmapSource;是否达到约 600 MB 取决于实际队列深度、图像尺寸和 GC 回收,需运行时采样确认 |
2. 审阅材料中的修正项
2.1 “_acquireQueue 和 ProcessFrameDequeued 都没有消费者”不准确(_acquireQueue 已于 2026-08-04 移除)
当时实现中:
_acquireQueue确实只有入队和容量淘汰,没有业务消费方;这是需要处理的资源保留问题。已修复:改为原子饱和计数器,不再持有帧对象。_processQueue由ProcessLoopAsync消费,并发布ProcessFrameDequeued;因此处理队列本身不是“只写不读”,未改动。ProcessFrameDequeued当前没有主项目订阅方,但它仍由处理线程发布,不能据此推断_processQueue未消费。
2.2 “错误事件完全没有订阅方”需要限定范围
射线源的 ErrorOccurredEvent 在模块自身已有 ViewModel 订阅,用于局部显示;更准确的问题是:主项目没有把运动、探测器和射线源故障统一映射到 SystemState,因此主界面和权限逻辑无法依赖统一故障状态。
2.3 “内存恒定约 600 MB”应改为容量上界风险(已通过移除 _acquireQueue 解决)
在 3072×3060、16 位图像下,单帧原始数组约 18.8 MB,BitmapSource 也可能占用相近量级。由于 _acquireQueue 容量为 16,静态上界确实可能达到数百 MB。2026-08-04 已移除该队列对帧对象的持有,改为纯计数,不再引入这部分常驻内存;P2-5 中提到的“每帧复制大数组”仍存在(用于生成 previewImage),但不再叠加队列滞留。
3. 建议修复顺序
第一阶段:解除当前功能阻塞 —— 已完成(2026-08-04)
处理已删除ExecuteRunBgaDetection的流水线结果缺失分支:明确显示错误并允许回退到受控的真实检测路径;删除无条件死代码return,不要静默结束。return,回退到真实检测路径。- 为 BGA 流水线缺失输出增加测试,覆盖“成功复用、缺失输出、输出类型错误”三种情况。未完成,本次未新增测试(
XplorePlane.Tests当前存在与本次改动无关的历史编译错误,阻塞了单测执行,详见第 4 节说明及第 5 节遗留事项)。
第二阶段:降低事件与内存压力 —— 已完成(2026-08-04)
将已改为值比较(MotionState去重从引用比较改为值比较,或在状态未变化时不创建新 record;同时增加事件频率测试。old == newState)。未新增专门的事件频率测试。明确确认只是调试计数用途,已移除队列本体,改为原子饱和计数器。_acquireQueue的职责:如果只是调试缓存,改为有限的轻量元数据/可配置采样;如果需要消费,则补齐消费者和释放策略。将已改为具名处理器 +PerformanceMonitorViewModel的匿名订阅改为具名处理器或保存SubscriptionToken,在Dispose中完整解除订阅。Dispose完整反订阅。
第三阶段:补齐状态闭环与生命周期
- 确定 CNC 执行状态的唯一事实源,统一
CncExecutionState与MainViewportService.IsCncRunning的读写路径。 - 将硬件错误事件统一映射到
SystemState,并明确 UI、日志和权限层的消费方。 - 为
ShowOrActivateVisible增加Closed清理;补充窗口重复打开、关闭后重开测试。 - 评估标定/画面联动 API 和带回调的 CNC 事件是否应改成专用服务或请求 ID。
4. 验收标准
- 静止设备不再因无变化的
MotionState每 100 ms 触发完整 UI 更新。(P0-2 已修复) - 调试面板关闭后,旧
PerformanceMonitorViewModel不再接收 AppState 事件。(P1-2 已修复) _acquireQueue有明确消费者或明确的淘汰/禁用策略,运行时内存曲线可解释。(P1-1 已修复:移除队列本体,改为纯计数)- BGA 流水线输出缺失时有可见错误,不再静默返回;编译器不再报告该方法的不可达代码。(P0-1 已修复)
- CNC 执行中权限判断、主界面状态和执行服务使用同一个状态来源。(P1-5/P1-7,第三阶段,未开始)
- 硬件故障能够进入统一状态、日志和 UI 告警链路。(P1-6,第三阶段,未开始)
第一、二阶段的四项已完成并通过 dotnet build 主项目编译验证;XplorePlane.Tests 因历史遗留的命名空间引用问题(XplorePlane.Services.Logging/Diagnostics/Dialogs 缺失)当前无法编译运行,与本次改动无关(改动前 git stash 验证同样报错),因此本轮未新增/运行自动化测试,也未验证 P1-3/P1-4/P2-3 等标注为“待验证”的问题。
5. 剩余事项(第三阶段,未开始)
- P1-3
_latestGeometry跨线程同步风险待量化。 - P1-4 探测器断连状态判断的原子性待确认。
- P1-5
CncExecutionStateChanged统一事实源。 - P1-6 硬件错误事件接入统一
SystemState。 - P1-7
OperationMode恒为Idle的权限守卫失效风险。 - P2-1 标定矩阵/画面联动子系统去留决策。
- P2-2
WindowLauncherService.ShowOrActivateVisible窗口生命周期清理。 - P2-3 事件投递顺序与属性值一致性待验证。
- P2-4
CncRoiEditRequestedPayload携带Action委托的设计风险。 - P2-5 探测器每帧复制大数组(
previewImage生成部分,与_acquireQueue无关的部分仍存在)。 - 修复
XplorePlane.Tests的历史编译错误,恢复单测可运行,补齐 P0-1/P0-2/P1-1/P1-2 对应的自动化测试。
6. 关联文档
doc/评估.md:项目整体评估和既有死代码/占位实现清单。doc/archive/状态与事件流架构.md:AppState 与事件流架构说明。doc/fix/调试面板无限弹窗修复说明.md:调试面板窗口生命周期背景。