Files
XplorePlane/doc/fix/AppState与事件流问题审计.md
T
2026-08-04 10:26:40 +08:00

11 KiB
Raw Blame History

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 == newStaterecord 自动生成 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:161AppStateService.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.IdlePermissionService.cs:159 依赖该字段
P2-1 标定矩阵/画面联动没有外部调用方 基本确认 UpdateCalibrationMatrixRequestLinkedViewUpdateLinkedViewState 的调用主要位于 AppStateService 自身,未发现业务入口;需结合实际 UI 需求决定是接入还是删除
P2-2 WindowLauncherService.ShowOrActivateVisible 关闭后保留窗口引用 确认存在 WindowLauncherService.cs:91-106 没有像 ShowOrActivate 一样挂载 Closed 清理逻辑;接口注释也明确记录了该历史行为
P2-3 事件投递顺序与最新属性值可能不一致 待验证 目前代码确实存在 Interlocked.ExchangeDispatcher.BeginInvoke 的组合,但是否造成实际错序需要并发测试或日志证据,不能仅凭静态审阅定性为已发生故障
P2-4 CncRoiEditRequestedPayload 携带 Action 委托 设计风险确认 CncEvents.cs:54-60 将回调放入全局事件 Payload,存在生命周期、重入和异常传播风险
P2-5 探测器每帧复制大数组并被多处缓存 部分确认 DetectorFramePipelineService.cs:99-114 每帧复制原始数组并生成 BitmapSource;是否达到约 600 MB 取决于实际队列深度、图像尺寸和 GC 回收,需运行时采样确认

2. 审阅材料中的修正项

2.1 “_acquireQueueProcessFrameDequeued 都没有消费者”不准确(_acquireQueue 已于 2026-08-04 移除)

当时实现中:

  • _acquireQueue 确实只有入队和容量淘汰,没有业务消费方;这是需要处理的资源保留问题。已修复:改为原子饱和计数器,不再持有帧对象。
  • _processQueueProcessLoopAsync 消费,并发布 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

  1. 处理 ExecuteRunBgaDetection 的流水线结果缺失分支:明确显示错误并允许回退到受控的真实检测路径;删除无条件死代码 return,不要静默结束。 已删除 return,回退到真实检测路径。
  2. 为 BGA 流水线缺失输出增加测试,覆盖“成功复用、缺失输出、输出类型错误”三种情况。未完成,本次未新增测试(XplorePlane.Tests 当前存在与本次改动无关的历史编译错误,阻塞了单测执行,详见第 4 节说明及第 5 节遗留事项)。

第二阶段:降低事件与内存压力 —— 已完成(2026-08-04

  1. MotionState 去重从引用比较改为值比较,或在状态未变化时不创建新 record;同时增加事件频率测试。 已改为值比较(old == newState)。未新增专门的事件频率测试。
  2. 明确 _acquireQueue 的职责:如果只是调试缓存,改为有限的轻量元数据/可配置采样;如果需要消费,则补齐消费者和释放策略。 确认只是调试计数用途,已移除队列本体,改为原子饱和计数器。
  3. PerformanceMonitorViewModel 的匿名订阅改为具名处理器或保存 SubscriptionToken,在 Dispose 中完整解除订阅。 已改为具名处理器 + Dispose 完整反订阅。

第三阶段:补齐状态闭环与生命周期

  1. 确定 CNC 执行状态的唯一事实源,统一 CncExecutionStateMainViewportService.IsCncRunning 的读写路径。
  2. 将硬件错误事件统一映射到 SystemState,并明确 UI、日志和权限层的消费方。
  3. ShowOrActivateVisible 增加 Closed 清理;补充窗口重复打开、关闭后重开测试。
  4. 评估标定/画面联动 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. 关联文档