From 0d1cda6403099026466867cf5d523514d7283935 Mon Sep 17 00:00:00 2001 From: "zhengxuan.zhang" Date: Tue, 4 Aug 2026 09:35:13 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E6=B5=81=E6=B0=B4=E7=BA=BF?= =?UTF-8?q?=E5=B1=9E=E6=80=A7=E9=9D=A2=E6=9D=BF=E5=AE=9E=E6=97=B6=E9=A2=84?= =?UTF-8?q?=E8=A7=88=E7=BC=BA=E5=A4=B1=E5=8F=A0=E5=8A=A0=E6=98=BE=E7=A4=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit CncInspectionModulePipelineViewModel.ExecutePreviewAsync 只推送了结果图, 未像 PipelineEditorViewModel 一样调用 PushDetectionOverlay, 导致调参实时预览时不会叠加显示 BGA 检测的圆形标注。 Co-Authored-By: Claude Sonnet 5 --- .../CncInspectionModulePipelineViewModel.cs | 8 +- doc/fix/AppState与事件流问题审计.md | 81 +++++++++++++++++++ 2 files changed, 88 insertions(+), 1 deletion(-) create mode 100644 doc/fix/AppState与事件流问题审计.md diff --git a/XplorePlane/ViewModels/Cnc/CncInspectionModulePipelineViewModel.cs b/XplorePlane/ViewModels/Cnc/CncInspectionModulePipelineViewModel.cs index 394f3412..ee2a3c27 100644 --- a/XplorePlane/ViewModels/Cnc/CncInspectionModulePipelineViewModel.cs +++ b/XplorePlane/ViewModels/Cnc/CncInspectionModulePipelineViewModel.cs @@ -685,9 +685,15 @@ namespace XplorePlane.ViewModels.Cnc try { _logger.Info("[图像链路][CNC] ExecutePreviewAsync:开始执行,节点数={Count}", PipelineNodes.Count); - var execResult = await _executionService.ExecutePipelineAsync(GetNodesInExecutionScope(), sourceImage, null, token); + var executionNodes = GetNodesInExecutionScope().ToList(); + var execResult = await _executionService.ExecutePipelineAsync(executionNodes, sourceImage, null, token); _logger.Info("[图像链路][CNC] ExecutePreviewAsync:执行完成,推送结果图像"); _mainViewportService.SetManualImage(execResult.Image, string.Empty); + if (execResult.LastStepOutputData != null) + { + var operatorKey = executionNodes.LastOrDefault()?.OperatorKey ?? string.Empty; + _mainViewportService.PushDetectionOverlay(execResult.LastStepOutputData, operatorKey); + } _eventAggregator?.GetEvent() .Publish(new PipelinePreviewUpdatedPayload(execResult.Image, StatusMessage)); } diff --git a/doc/fix/AppState与事件流问题审计.md b/doc/fix/AppState与事件流问题审计.md new file mode 100644 index 00000000..3502b432 --- /dev/null +++ b/doc/fix/AppState与事件流问题审计.md @@ -0,0 +1,81 @@ +# AppState 与事件流问题审计 + +> 审计日期:2026-08-03 +> 审计范围:硬件事件、`AppStateService`、调试面板、探测器帧队列、BGA 向导及窗口生命周期。 +> 本文用于记录代码审阅结论,不代表问题已经修复。 + +## 1. 审计结论 + +整体架构判断成立:硬件层通过 Prism 事件进入 `AppStateService`,再由状态事件通知 ViewModel。当前发现的问题主要集中在事件去重、资源生命周期和未完成的回退路径,不属于架构需要整体重写的问题。 + +审阅材料中的结论需要按下面的状态理解: + +| 编号 | 问题 | 当前判断 | 证据 | +|---|---|---|---| +| P0-1 | BGA 检测方法存在无条件 `return`,流水线结果缺失时静默退出 | **确认存在** | [`InspectionTaskWizardViewModel.cs:638`](../../XplorePlane/ViewModels/Inspection/TaskWizard/InspectionTaskWizardViewModel.cs#L638);后续代码已被编译器报 `CS0162` | +| P0-2 | `MotionState` 事件可能无变化洪泛 | **确认存在** | `BuildMotionStateSnapshot` 每次生成新的 record;[`AppStateService.cs:790-794`](../../XplorePlane/Services/AppState/AppStateService.cs#L790) 使用 `ReferenceEquals` 去重 | +| P1-1 | `_acquireQueue` 长期保留大尺寸帧 | **确认存在,需先验证运行时占用** | [`DetectorFramePipelineService.cs:29`](../../XplorePlane/Services/Main/Viewport/DetectorFramePipelineService.cs#L29) 入队后没有对应出队;处理队列 `_processQueue` 则有后台消费者,不应把两者混为一谈 | +| P1-2 | 调试面板 `PerformanceMonitorViewModel` 事件订阅无法解除 | **确认存在** | [`PerformanceMonitorViewModel.cs:84-90`](../../XplorePlane/ViewModels/Debug/PerformanceMonitorViewModel.cs#L84) 使用匿名 lambda;[`Dispose`](../../XplorePlane/ViewModels/Debug/PerformanceMonitorViewModel.cs#L96) 未反订阅 | +| P1-3 | `_latestGeometry` 跨线程读写缺少明确同步 | **确认存在,风险待量化** | [`AppStateService.cs:161`](../../XplorePlane/Services/AppState/AppStateService.cs#L161)、[`AppStateService.cs:566-571`](../../XplorePlane/Services/AppState/AppStateService.cs#L566) | +| P1-4 | 探测器断连状态判断不是单一原子事务 | **部分确认** | [`AppStateService.cs:579-585`](../../XplorePlane/Services/AppState/AppStateService.cs#L579) 在后台线程读取 `_detectorState`;最终状态更新仍通过原子替换,但 `wasConnected` 与后续更新之间可能交错 | +| P1-5 | `CncExecutionStateChanged` 没有主项目订阅方 | **确认存在** | `AppStateService` 有发布事件,但当前主项目未发现 `+=` 订阅;部分界面改读 `MainViewportService.IsCncRunning`,形成双来源风险 | +| P1-6 | 硬件错误事件没有进入统一 `SystemState` | **确认存在** | `MotionErrorEvent`、探测器 `ErrorOccurredEvent`、射线源 `ErrorOccurredEvent` 在硬件层有发布,但主项目未发现对应统一订阅;射线源自身 ViewModel 的局部订阅不等于全局故障状态闭环 | +| P1-7 | `OperationMode` 始终为 `Idle`,权限守卫可能失效 | **确认存在** | 当前唯一写入点 [`RecipeService.cs:225`](../../XplorePlane/Services/Recipe/RecipeService.cs#L225) 仍写入 `OperationMode.Idle`;[`PermissionService.cs:159`](../../XplorePlane/Services/Security/PermissionService.cs#L159) 依赖该字段 | +| P2-1 | 标定矩阵/画面联动没有外部调用方 | **基本确认** | `UpdateCalibrationMatrix`、`RequestLinkedView`、`UpdateLinkedViewState` 的调用主要位于 `AppStateService` 自身,未发现业务入口;需结合实际 UI 需求决定是接入还是删除 | +| P2-2 | `WindowLauncherService.ShowOrActivateVisible` 关闭后保留窗口引用 | **确认存在** | [`WindowLauncherService.cs:91-106`](../../XplorePlane/Services/Main/WindowLauncherService.cs#L91) 没有像 `ShowOrActivate` 一样挂载 `Closed` 清理逻辑;接口注释也明确记录了该历史行为 | +| P2-3 | 事件投递顺序与最新属性值可能不一致 | **待验证** | 目前代码确实存在 `Interlocked.Exchange` 与 `Dispatcher.BeginInvoke` 的组合,但是否造成实际错序需要并发测试或日志证据,不能仅凭静态审阅定性为已发生故障 | +| P2-4 | `CncRoiEditRequestedPayload` 携带 `Action` 委托 | **设计风险确认** | [`CncEvents.cs:54-60`](../../XplorePlane/Events/CncEvents.cs#L54) 将回调放入全局事件 Payload,存在生命周期、重入和异常传播风险 | +| P2-5 | 探测器每帧复制大数组并被多处缓存 | **部分确认** | [`DetectorFramePipelineService.cs:99-114`](../../XplorePlane/Services/Main/Viewport/DetectorFramePipelineService.cs#L99) 每帧复制原始数组并生成 BitmapSource;是否达到约 600 MB 取决于实际队列深度、图像尺寸和 GC 回收,需运行时采样确认 | + +## 2. 审阅材料中的修正项 + +### 2.1 “`_acquireQueue` 和 `ProcessFrameDequeued` 都没有消费者”不准确 + +当前实现中: + +- `_acquireQueue` 确实只有入队和容量淘汰,没有业务消费方;这是需要处理的资源保留问题。 +- `_processQueue` 由 `ProcessLoopAsync` 消费,并发布 `ProcessFrameDequeued`;因此处理队列本身不是“只写不读”。 +- `ProcessFrameDequeued` 当前没有主项目订阅方,但它仍由处理线程发布,不能据此推断 `_processQueue` 未消费。 + +### 2.2 “错误事件完全没有订阅方”需要限定范围 + +射线源的 `ErrorOccurredEvent` 在模块自身已有 ViewModel 订阅,用于局部显示;更准确的问题是:主项目没有把运动、探测器和射线源故障统一映射到 `SystemState`,因此主界面和权限逻辑无法依赖统一故障状态。 + +### 2.3 “内存恒定约 600 MB”应改为容量上界风险 + +在 3072×3060、16 位图像下,单帧原始数组约 18.8 MB,BitmapSource 也可能占用相近量级。由于 `_acquireQueue` 容量为 16,静态上界确实可能达到数百 MB;但实际占用还受 BitmapSource 内存布局、淘汰时机和 GC 影响,应通过运行时计数器确认后再作为验收数字。 + +## 3. 建议修复顺序 + +### 第一阶段:解除当前功能阻塞 + +1. 处理 `ExecuteRunBgaDetection` 的流水线结果缺失分支:明确显示错误并允许回退到受控的真实检测路径;删除无条件死代码 `return`,不要静默结束。 +2. 为 BGA 流水线缺失输出增加测试,覆盖“成功复用、缺失输出、输出类型错误”三种情况。 + +### 第二阶段:降低事件与内存压力 + +1. 将 `MotionState` 去重从引用比较改为值比较,或在状态未变化时不创建新 record;同时增加事件频率测试。 +2. 明确 `_acquireQueue` 的职责:如果只是调试缓存,改为有限的轻量元数据/可配置采样;如果需要消费,则补齐消费者和释放策略。 +3. 将 `PerformanceMonitorViewModel` 的匿名订阅改为具名处理器或保存 `SubscriptionToken`,在 `Dispose` 中完整解除订阅。 + +### 第三阶段:补齐状态闭环与生命周期 + +1. 确定 CNC 执行状态的唯一事实源,统一 `CncExecutionState` 与 `MainViewportService.IsCncRunning` 的读写路径。 +2. 将硬件错误事件统一映射到 `SystemState`,并明确 UI、日志和权限层的消费方。 +3. 为 `ShowOrActivateVisible` 增加 `Closed` 清理;补充窗口重复打开、关闭后重开测试。 +4. 评估标定/画面联动 API 和带回调的 CNC 事件是否应改成专用服务或请求 ID。 + +## 4. 验收标准 + +- 静止设备不再因无变化的 `MotionState` 每 100 ms 触发完整 UI 更新。 +- 调试面板关闭后,旧 `PerformanceMonitorViewModel` 不再接收 AppState 事件。 +- `_acquireQueue` 有明确消费者或明确的淘汰/禁用策略,运行时内存曲线可解释。 +- BGA 流水线输出缺失时有可见错误,不再静默返回;编译器不再报告该方法的不可达代码。 +- CNC 执行中权限判断、主界面状态和执行服务使用同一个状态来源。 +- 硬件故障能够进入统一状态、日志和 UI 告警链路。 + +## 5. 关联文档 + +- [`doc/评估.md`](../评估.md):项目整体评估和既有死代码/占位实现清单。 +- [`doc/archive/状态与事件流架构.md`](../archive/状态与事件流架构.md):AppState 与事件流架构说明。 +- [`doc/fix/调试面板无限弹窗修复说明.md`](调试面板无限弹窗修复说明.md):调试面板窗口生命周期背景。