diff --git a/UIShare/GlobalVariable/HardwareDataBroadcaster.cs b/UIShare/GlobalVariable/HardwareDataBroadcaster.cs index a5e0fbf..2380403 100644 --- a/UIShare/GlobalVariable/HardwareDataBroadcaster.cs +++ b/UIShare/GlobalVariable/HardwareDataBroadcaster.cs @@ -1,8 +1,10 @@ using Common.Attributes; using DeviceCommand.Base; +using Logger; using Model.Models; using Prism.Events; using System; +using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; using System.Linq.Expressions; @@ -41,6 +43,18 @@ namespace UIShare.GlobalVariable private CancellationTokenSource? _cts; private bool _disposed; + /// 是否已完成过至少一次 Discover(幂等标记,避免多作用域重复调用时 Clear + 重扫) + private bool _discovered; + + /// 连续失败次数达到此阈值后,暂停该通道采样并发布报警 + private const int FailureThreshold = 5; + + /// 每通道连续失败计数(key = fingerprint + "|" + methodName) + private readonly ConcurrentDictionary _failureCounts = new(); + + /// 已因连续失败而被暂停的通道(key 同上) + private readonly ConcurrentDictionary _suspendedChannels = new(); + /// 采样间隔(默认 1000ms) public TimeSpan SampleInterval { @@ -89,9 +103,12 @@ namespace UIShare.GlobalVariable /// /// 扫描 GlobalInfo.HardwarePool 中所有已创建的物理设备, /// 反查可监测方法并编译为委托。每个指纹只注册一次,反射只执行一次。 + /// 幂等:首次调用后再次调用不会 Clear 重扫,避免多作用域重复 Discover 导致短暂采样中断。 /// public void Discover() { + if (_discovered) return; + _discovered = true; _registeredMethods.Clear(); foreach (var poolEntry in _globalInfo.HardwarePool) @@ -157,6 +174,11 @@ namespace UIShare.GlobalVariable foreach (var entry in _registeredMethods) { + string channelKey = entry.Fingerprint + "|" + entry.MethodName; + + // 已暂停的通道跳过采样 + if (_suspendedChannels.ContainsKey(channelKey)) continue; + // fire-and-forget:每个通道独立采样,完成后自行广播 _ = Task.Run(async () => { @@ -167,6 +189,9 @@ namespace UIShare.GlobalVariable if (!double.TryParse(raw, out double value)) return; + // 采样成功,重置失败计数 + _failureCounts.TryRemove(channelKey, out _); + // 向所有引用该物理设备的作用域分别广播 var scopes = GetScopesForFingerprint(entry.Fingerprint); foreach (var scope in scopes) @@ -188,9 +213,24 @@ namespace UIShare.GlobalVariable } } } - catch + catch (Exception ex) { - // 单个通道故障不干扰其他通道 + int count = _failureCounts.AddOrUpdate(channelKey, 1, (_, c) => c + 1); + LoggerHelper.Warn($"采样通道 [{entry.Fingerprint}/{entry.MethodName}] 第 {count} 次失败: {ex.Message}"); + + if (count >= FailureThreshold) + { + _suspendedChannels.TryAdd(channelKey, true); + LoggerHelper.Error($"采样通道 [{entry.Fingerprint}/{entry.MethodName}] 连续失败 {count} 次,已暂停采样"); + + // 向所有引用该设备的作用域发布报警 + var scopes = GetScopesForFingerprint(entry.Fingerprint); + foreach (var scope in scopes) + { + _eventAggregator.GetEvent().Publish( + (scope, entry.Fingerprint, $"通道 {entry.MethodName} 连续失败 {count} 次,已暂停")); + } + } } }, token); } @@ -209,6 +249,8 @@ namespace UIShare.GlobalVariable _cts?.Dispose(); _cts = null; _registeredMethods.Clear(); + _failureCounts.Clear(); + _suspendedChannels.Clear(); } } }