211 lines
8.2 KiB
C#
211 lines
8.2 KiB
C#
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<object, CancellationToken, Task<string>>
|
||
/// - 第一个参数 = 设备实例(open delegate 风格,编译时已做类型转换)
|
||
/// - 第二个参数 = CancellationToken(无参方法会忽略它)
|
||
/// - 返回值 = Task<string>
|
||
/// </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<object, CancellationToken, Task<string>>:
|
||
/// - 无参方法:忽略 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 是否超限
|
||
ValueLimitAlarmHelper.CheckAlarm(scope, entry.Fingerprint, entry.MethodName, value, _globalInfo);
|
||
}
|
||
}
|
||
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();
|
||
}
|
||
}
|
||
}
|