上报数据方式优化
This commit is contained in:
207
UIShare/GlobalVariable/HardwareDataBroadcaster.cs
Normal file
207
UIShare/GlobalVariable/HardwareDataBroadcaster.cs
Normal file
@@ -0,0 +1,207 @@
|
||||
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
|
||||
});
|
||||
}
|
||||
}
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user