修复 AppState 审计第一/第二阶段问题:功能阻塞与事件/内存压力

- BGA 检测:流水线复用失败时删除无条件 return 死代码,回退到真实检测路径(原有仿真数据兜底不变)
- MotionState 去重改用值比较(record Equals),避免设备静止时 100ms 轮询仍每次触发全量 UI 更新
- DetectorFramePipelineService 移除只写不读的 _acquireQueue,改为原子饱和计数器,不再让约 37MB/帧的图像常驻内存
- PerformanceMonitorViewModel 订阅事件改为具名处理器,Dispose 时完整反订阅,避免调试面板重复开关造成泄漏

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
zhengxuan.zhang
2026-08-04 10:20:38 +08:00
parent c17c76e90f
commit f2ca9cb611
4 changed files with 43 additions and 13 deletions
@@ -790,7 +790,7 @@ namespace XplorePlane.Services.AppState
private void SetMotionState(MotionState newState)
{
var old = Interlocked.Exchange(ref _motionState, newState);
if (ReferenceEquals(old, newState)) return;
if (old == newState) return;
RaiseOnDispatcher(old, newState, MotionStateChanged, nameof(MotionState));
}
}
@@ -26,7 +26,6 @@ namespace XplorePlane.Services.Main.Viewport
public sealed class DetectorFramePipelineService : IDetectorFramePipelineService
{
private readonly ConcurrentQueue<DetectorFrame> _acquireQueue = new();
private readonly ConcurrentQueue<DetectorFrame> _processQueue = new();
private readonly SemaphoreSlim _processSignal = new(0);
private readonly CancellationTokenSource _shutdown = new();
@@ -110,7 +109,8 @@ namespace XplorePlane.Services.Main.Viewport
rawPixels: rawPixels,
previewImage: bitmap);
EnqueueBounded(_acquireQueue, frame, AcquireQueueCapacity, ref _acquireQueueCount);
// 仅维护饱和计数用于诊断展示,不持有帧对象本身,避免大尺寸帧在无消费者的情况下常驻内存。
IncrementSaturating(ref _acquireQueueCount, AcquireQueueCapacity);
_mainViewportService.UpdateDetectorFrame(frame);
var sequence = Interlocked.Increment(ref _receivedFrameCount);
@@ -168,6 +168,18 @@ namespace XplorePlane.Services.Main.Viewport
}
}
/// <summary>递增计数并夹到容量上限,用于无实际队列时的诊断计数。</summary>
private static void IncrementSaturating(ref int count, int capacity)
{
int current, next;
do
{
current = Volatile.Read(ref count);
next = Math.Min(current + 1, capacity);
}
while (Interlocked.CompareExchange(ref count, next, current) != current);
}
private static int ReadInt(string key, int defaultValue, int minValue)
{
var raw = ConfigurationManager.AppSettings[key];
@@ -81,19 +81,27 @@ namespace XplorePlane.ViewModels.Debug
return;
}
_appStateService.MotionStateChanged += (_, _) => RecordEvent("MotionState");
_appStateService.RaySourceStateChanged += (_, _) => RecordEvent("RaySourceState");
_appStateService.DetectorStateChanged += (_, _) => RecordEvent("DetectorState");
_appStateService.SystemStateChanged += (_, _) => RecordEvent("SystemState");
_appStateService.CameraStateChanged += (_, _) => RecordEvent("CameraState");
_appStateService.LinkedViewStateChanged += (_, _) => RecordEvent("LinkedViewState");
_appStateService.RecipeExecutionStateChanged += (_, _) => RecordEvent("RecipeExecutionState");
_appStateService.MotionStateChanged += OnMotionStateChanged;
_appStateService.RaySourceStateChanged += OnRaySourceStateChanged;
_appStateService.DetectorStateChanged += OnDetectorStateChanged;
_appStateService.SystemStateChanged += OnSystemStateChanged;
_appStateService.CameraStateChanged += OnCameraStateChanged;
_appStateService.LinkedViewStateChanged += OnLinkedViewStateChanged;
_appStateService.RecipeExecutionStateChanged += OnRecipeExecutionStateChanged;
_timer.Start();
_initialized = true;
}
/// <summary>释放资源:停止定时器 + 清空数据</summary>
private void OnMotionStateChanged(object sender, StateChangedEventArgs<XplorePlane.Models.Main.MotionState> e) => RecordEvent("MotionState");
private void OnRaySourceStateChanged(object sender, StateChangedEventArgs<XplorePlane.Models.Main.RaySourceState> e) => RecordEvent("RaySourceState");
private void OnDetectorStateChanged(object sender, StateChangedEventArgs<XplorePlane.Models.Main.DetectorState> e) => RecordEvent("DetectorState");
private void OnSystemStateChanged(object sender, StateChangedEventArgs<XplorePlane.Models.Main.SystemState> e) => RecordEvent("SystemState");
private void OnCameraStateChanged(object sender, StateChangedEventArgs<XplorePlane.Models.Main.CameraState> e) => RecordEvent("CameraState");
private void OnLinkedViewStateChanged(object sender, StateChangedEventArgs<XplorePlane.Models.Main.LinkedViewState> e) => RecordEvent("LinkedViewState");
private void OnRecipeExecutionStateChanged(object sender, StateChangedEventArgs<XplorePlane.Models.Recipe.RecipeExecutionState> e) => RecordEvent("RecipeExecutionState");
/// <summary>释放资源:反订阅状态事件 + 停止定时器 + 清空数据</summary>
public void Dispose()
{
if (_disposed)
@@ -101,6 +109,17 @@ namespace XplorePlane.ViewModels.Debug
return;
}
if (_initialized)
{
_appStateService.MotionStateChanged -= OnMotionStateChanged;
_appStateService.RaySourceStateChanged -= OnRaySourceStateChanged;
_appStateService.DetectorStateChanged -= OnDetectorStateChanged;
_appStateService.SystemStateChanged -= OnSystemStateChanged;
_appStateService.CameraStateChanged -= OnCameraStateChanged;
_appStateService.LinkedViewStateChanged -= OnLinkedViewStateChanged;
_appStateService.RecipeExecutionStateChanged -= OnRecipeExecutionStateChanged;
}
_timer.Stop();
Metrics.Clear();
TrendData.Clear();
@@ -634,12 +634,11 @@ namespace XplorePlane.ViewModels.Inspection.TaskWizard
}
_logger.LogWarning(
"[BGA-Pipeline] 未找到流水线 BgaVoidRate 输出,operator={Operator}, keys={Keys}",
"[BGA-Pipeline] 未找到流水线 BgaVoidRate 输出,operator={Operator}, keys={Keys},回退到真实检测路径",
_viewportService?.LatestPipelineOperatorKey ?? string.Empty,
_viewportService?.LatestPipelineOutputData == null
? "none"
: string.Join(",", _viewportService.LatestPipelineOutputData.Keys));
return;
_logger.LogInformation("▶ ExecuteRunBgaDetection 开始");