Files
ACP/UIShare/GlobalVariable/HardwareDataBroadcaster.cs
2026-07-29 13:12:27 +08:00

215 lines
8.5 KiB
C#
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using Common.Attributes;
using DeviceCommand.Base;
using Model.Models;
using Prism.Events;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Threading;
using UIShare.PubEvent;
namespace UIShare.GlobalVariable
{
/// <summary>
/// 硬件数据广播器(中间层,全局单例):
/// 直接基于 GlobalInfo.HardwarePool 中的设备指纹字典进行统一采样,
/// 每个物理设备只采样一次,然后向所有引用该设备的作用域分别广播 HardwareDataReportedEvent。
/// 解决多作用域共享同一物理设备时的重复采样问题。
/// DeviceCommand 层无需引用 Prism完全解耦。
/// </summary>
public class HardwareDataBroadcaster : IDisposable
{
private readonly IEventAggregator _eventAggregator;
private readonly GlobalInfo _globalInfo;
/// <summary>采样定时器</summary>
private readonly DispatcherTimer _sampleTimer;
/// <summary>
/// 已注册的采样项:(Fingerprint, MethodName, 编译后的委托, DeviceInstance)
/// 委托签名统一为 Func&lt;object, CancellationToken, Task&lt;string&gt;&gt;
/// - 第一个参数 = 设备实例open delegate 风格,编译时已做类型转换)
/// - 第二个参数 = CancellationToken无参方法会忽略它
/// - 返回值 = Task&lt;string&gt;
/// </summary>
private readonly List<(string Fingerprint, string MethodName, Func<object, CancellationToken, Task<string>> Invoker, object Device)> _registeredMethods = new();
private CancellationTokenSource? _cts;
private bool _disposed;
/// <summary>采样间隔(默认 1000ms</summary>
public TimeSpan SampleInterval
{
get => _sampleTimer.Interval;
set => _sampleTimer.Interval = value;
}
public HardwareDataBroadcaster(GlobalInfo globalInfo, IEventAggregator eventAggregator)
{
_globalInfo = globalInfo;
_eventAggregator = eventAggregator;
_sampleTimer = new DispatcherTimer(DispatcherPriority.Normal)
{
Interval = TimeSpan.FromMilliseconds(1000)
};
_sampleTimer.Tick += OnSampleTick;
}
/// <summary>
/// 将 MethodInfo 编译为强类型委托,后续调用零反射开销。
/// 统一签名为 Func&lt;object, CancellationToken, Task&lt;string&gt;&gt;
/// - 无参方法:忽略 CancellationToken
/// - 带 CancellationToken 方法:直接透传
/// </summary>
private static Func<object, CancellationToken, Task<string>> BuildInvoker(MethodInfo method)
{
var deviceParam = Expression.Parameter(typeof(object), "device");
var ctParam = Expression.Parameter(typeof(CancellationToken), "ct");
// 将 object 转型为方法的声明类型
var castDevice = Expression.Convert(deviceParam, method.DeclaringType!);
// 根据方法签名决定传参
var parms = method.GetParameters();
Expression[] callArgs = parms.Length == 1
? new Expression[] { ctParam }
: Array.Empty<Expression>();
var call = Expression.Call(castDevice, method, callArgs);
return Expression.Lambda<Func<object, CancellationToken, Task<string>>>(
call, deviceParam, ctParam).Compile();
}
/// <summary>
/// 扫描 GlobalInfo.HardwarePool 中所有已创建的物理设备,
/// 反查可监测方法并编译为委托。每个指纹只注册一次,反射只执行一次。
/// </summary>
public void Discover()
{
_registeredMethods.Clear();
foreach (var poolEntry in _globalInfo.HardwarePool)
{
string fingerprint = poolEntry.Key;
var lazy = poolEntry.Value;
// 只监控已经实例化的设备
if (!lazy.IsValueCreated || lazy.Value == null) continue;
var device = lazy.Value;
var deviceType = device.GetType();
var methods = deviceType.GetMethods(BindingFlags.Public | BindingFlags.Instance)
.Where(m =>
{
if (m.GetCustomAttribute<MonitorableAttribute>() == null) return false;
if (m.ReturnType != typeof(Task<string>)) return false;
var parms = m.GetParameters();
return parms.Length == 0 ||
(parms.Length == 1 && parms[0].ParameterType == typeof(CancellationToken));
});
foreach (var method in methods)
{
var invoker = BuildInvoker(method);
_registeredMethods.Add((fingerprint, method.Name, invoker, device));
}
}
}
/// <summary>获取指定硬件指纹当前被哪些作用域引用</summary>
private List<string> GetScopesForFingerprint(string fingerprint)
{
if (_globalInfo.DeviceAndScopeDic.TryGetValue(fingerprint, out var lazy))
{
var scopeList = lazy.Value;
lock (scopeList) return scopeList.ToList();
}
return new List<string>();
}
/// <summary>启动采样广播(幂等)</summary>
public void Start()
{
if (_disposed) return;
_cts ??= new CancellationTokenSource();
if (!_sampleTimer.IsEnabled) _sampleTimer.Start();
}
/// <summary>停止采样</summary>
public void Stop()
{
_sampleTimer.Stop();
}
private void OnSampleTick(object? sender, EventArgs e)
{
if (_registeredMethods.Count == 0 || _disposed) return;
var token = _cts?.Token ?? CancellationToken.None;
var now = DateTime.Now;
foreach (var entry in _registeredMethods)
{
// fire-and-forget每个通道独立采样完成后自行广播
_ = Task.Run(async () =>
{
try
{
// 直接委托调用,零反射开销
string raw = await entry.Invoker(entry.Device, token).ConfigureAwait(false);
if (!double.TryParse(raw, out double value)) return;
// 向所有引用该物理设备的作用域分别广播
var scopes = GetScopesForFingerprint(entry.Fingerprint);
foreach (var scope in scopes)
{
_eventAggregator.GetEvent<HardwareDataReportedEvent>().Publish(new HardwareReportArgs
{
Scope = scope,
HardwareFingerprint = entry.Fingerprint,
MethodName = entry.MethodName,
Value = value,
Time = now
});
// 同步检查该作用域 ValueLimitList 是否超限
string MonitorStatus= ValueLimitAlarmHelper.CheckAlarm(scope, entry.Fingerprint, entry.MethodName, value, _globalInfo);
if (MonitorStatus != "" && MonitorStatus != "未报警")
{
_eventAggregator.GetEvent<AlarmEvent>().Publish((scope,entry.Fingerprint, MonitorStatus));
}
}
}
catch
{
// 单个通道故障不干扰其他通道
}
}, token);
}
// 不在 UI 线程 await——任务在线程池上自行完成并广播事件
// DispatcherTimer 按设定间隔准时触发下一次 Tick。
}
public void Dispose()
{
if (_disposed) return;
_disposed = true;
_sampleTimer.Stop();
_sampleTimer.Tick -= OnSampleTick;
try { _cts?.Cancel(); } catch { }
_cts?.Dispose();
_cts = null;
_registeredMethods.Clear();
}
}
}