diff --git a/src/Admin/ThingsGateway.Admin.Application/ThingsGateway.Admin.Application.csproj b/src/Admin/ThingsGateway.Admin.Application/ThingsGateway.Admin.Application.csproj index de1d301dc..1fdd773b3 100644 --- a/src/Admin/ThingsGateway.Admin.Application/ThingsGateway.Admin.Application.csproj +++ b/src/Admin/ThingsGateway.Admin.Application/ThingsGateway.Admin.Application.csproj @@ -20,7 +20,7 @@ - + diff --git a/src/Admin/ThingsGateway.NewLife.X/Common/ExpiringDictionary.cs b/src/Admin/ThingsGateway.NewLife.X/Common/ExpiringDictionary.cs index 4caa54ffc..e20ed8780 100644 --- a/src/Admin/ThingsGateway.NewLife.X/Common/ExpiringDictionary.cs +++ b/src/Admin/ThingsGateway.NewLife.X/Common/ExpiringDictionary.cs @@ -5,30 +5,101 @@ namespace ThingsGateway.NewLife; public class ExpiringDictionary : IDisposable { - private ConcurrentDictionary _dict = new(); - private readonly TimerX _cleanupTimer; - - public ExpiringDictionary(int cleanupInterval = 60000) + /// 缓存项 + public class CacheItem { - _cleanupTimer = new TimerX(Clear, null, cleanupInterval, cleanupInterval) { Async = true }; + private TValue? _value; + /// 数值 + public TValue? Value { get => _value; } + + /// 过期时间。系统启动以来的毫秒数 + public Int64 ExpiredTime { get; set; } + + /// 是否过期 + public Boolean Expired => ExpiredTime <= Runtime.TickCount64; + + /// 访问时间 + public Int64 VisitTime { get; private set; } + + /// 构造缓存项 + /// + /// + public CacheItem(TValue? value, Int32 expire) => Set(value, expire); + + /// 设置数值和过期时间 + /// + /// 过期时间,秒 + public void Set(TValue value, Int32 expire) + { + _value = value; + + var now = VisitTime = Runtime.TickCount64; + if (expire <= 0) + ExpiredTime = Int64.MaxValue; + else + ExpiredTime = now + expire * 1000L; + } + + /// 更新访问时间并返回数值 + /// + public TValue? Visit() + { + VisitTime = Runtime.TickCount64; + var rs = _value; + if (rs == null) return default; + + return rs; + } } - public void TryAdd(TKey key, TValue value) + private ConcurrentDictionary _dict = new(); + private readonly TimerX _cleanupTimer; + private int defaultExpire = 60; + public ExpiringDictionary(int expire = 60) { - _dict.TryAdd(key, value); + defaultExpire = expire; + _cleanupTimer = new TimerX(TimerClear, null, 10000, 10000) { Async = true }; + } + + + + public bool TryAdd(TKey key, TValue value) + { + if (_dict.TryGetValue(key, out var item)) + { + if (!item.Expired) return false; + item.Set(value, defaultExpire); + return true; + } + return _dict.TryAdd(key, new CacheItem(value, defaultExpire)); } public bool TryGetValue(TKey key, out TValue value) { - return _dict.TryGetValue(key, out value); + value = default; + + // 没有值,直接结束 + if (!_dict.TryGetValue(key, out var item) || item == null) return false; + + // 得到已有值 + value = item.Visit(); + + // 是否未过期的有效值 + return !item.Expired; } public TValue GetOrAdd(TKey key, Func func) { - return _dict.GetOrAdd(key, func); - } - public TValue GetOrAdd(TKey key, TValue value) - { - return _dict.GetOrAdd(key, value); + CacheItem? item = null; + do + { + if (_dict.TryGetValue(key, out item) && item != null) return item.Visit(); + + item ??= new CacheItem(func(key), defaultExpire); + } + while (!_dict.TryAdd(key, item)); + + return item.Visit(); + } public bool TryRemove(TKey key) => _dict.TryRemove(key, out _); @@ -41,7 +112,31 @@ public class ExpiringDictionary : IDisposable _dict = new(); data.Clear(); } + private void TimerClear(object? state) + { + var dic = _dict; + if (dic.IsEmpty) return; + + // 60分钟之内过期的数据,进入LRU淘汰 + var now = Runtime.TickCount64; + + // 这里先计算,性能很重要 + var toDels = new List(); + foreach (var item in dic) + { + // 已过期,准备删除 + var ci = item.Value; + if (ci.ExpiredTime <= now) + toDels.Add(item.Key); + } + + // 确认删除 + foreach (var item in toDels) + { + _dict.Remove(item); + } + } public void Dispose() { _dict.Clear(); diff --git a/src/Admin/ThingsGateway.NewLife.X/Reflection/Reflect.cs b/src/Admin/ThingsGateway.NewLife.X/Reflection/Reflect.cs index 895d30a1a..f7b290317 100644 --- a/src/Admin/ThingsGateway.NewLife.X/Reflection/Reflect.cs +++ b/src/Admin/ThingsGateway.NewLife.X/Reflection/Reflect.cs @@ -561,7 +561,7 @@ public static class Reflect /// /// /// - public static TFunc? As(this MethodInfo method, object? target = null) + public static TFunc? As(this MethodInfo method, object? target = null) where TFunc : class { if (method == null) return default; @@ -569,10 +569,14 @@ public static class Reflect var func = DelegateCache.Cache.GetOrAdd( key, - _ => (TFunc)(object)( + _ => + { + return ( target == null - ? Delegate.CreateDelegate(typeof(TFunc), method, true) - : Delegate.CreateDelegate(typeof(TFunc), target, method, true))); + ? Delegate.CreateDelegate(typeof(TFunc), method, true) as TFunc + : Delegate.CreateDelegate(typeof(TFunc), target, method, true) as TFunc + ); + }); return func; } diff --git a/src/Admin/ThingsGateway.NewLife.X/Threading/TimerX.cs b/src/Admin/ThingsGateway.NewLife.X/Threading/TimerX.cs index b4144e9e7..b4c5c7041 100644 --- a/src/Admin/ThingsGateway.NewLife.X/Threading/TimerX.cs +++ b/src/Admin/ThingsGateway.NewLife.X/Threading/TimerX.cs @@ -391,11 +391,7 @@ public class TimerX : ITimer, ITimerx, IDisposable // 释放非托管资源 Scheduler?.Remove(this, disposing ? "Dispose" : "GC"); - DelegateCache.Cache.Clear(); -#if NET6_0_OR_GREATER - DelegateCache>.Cache.Clear(); -#endif - DelegateCache>.Cache.Clear(); + } diff --git a/src/Directory.Build.props b/src/Directory.Build.props index 6b6d137d4..29468617f 100644 --- a/src/Directory.Build.props +++ b/src/Directory.Build.props @@ -1,9 +1,9 @@ - 10.11.97 - 10.11.97 - 10.11.97 + 10.11.98 + 10.11.98 + 10.11.98 10.11.6 10.11.6 8.0.20 @@ -12,7 +12,7 @@ false 10.11.87 10.11.87 - 4.0.0-beta.115 + 4.0.0-beta.120 diff --git a/src/Gateway/ThingsGateway.Gateway.Application/ThingsGateway.Gateway.Application.csproj b/src/Gateway/ThingsGateway.Gateway.Application/ThingsGateway.Gateway.Application.csproj index cf6c45519..8687c0712 100644 --- a/src/Gateway/ThingsGateway.Gateway.Application/ThingsGateway.Gateway.Application.csproj +++ b/src/Gateway/ThingsGateway.Gateway.Application/ThingsGateway.Gateway.Application.csproj @@ -9,7 +9,7 @@ - + diff --git a/src/Gateway/ThingsGateway.Gateway.Razor/Pages/GatewayMonitorPage/Variable/VariableRuntimeInfo.razor b/src/Gateway/ThingsGateway.Gateway.Razor/Pages/GatewayMonitorPage/Variable/VariableRuntimeInfo.razor index 9a892aea5..e7d73593e 100644 --- a/src/Gateway/ThingsGateway.Gateway.Razor/Pages/GatewayMonitorPage/Variable/VariableRuntimeInfo.razor +++ b/src/Gateway/ThingsGateway.Gateway.Razor/Pages/GatewayMonitorPage/Variable/VariableRuntimeInfo.razor @@ -21,7 +21,7 @@ ShowToolbar="true" ShowExportButton IsAutoRefresh - AutoRefreshInterval="2000" + AutoRefreshInterval="1000" ShowDefaultButtons=true ShowSearch=false ExtendButtonColumnWidth=220 diff --git a/src/Gateway/ThingsGateway.Gateway.Razor/Pages/GatewayMonitorPage/Variable/VariableRuntimeInfo.razor.cs b/src/Gateway/ThingsGateway.Gateway.Razor/Pages/GatewayMonitorPage/Variable/VariableRuntimeInfo.razor.cs index 9802cd965..15760b3d4 100644 --- a/src/Gateway/ThingsGateway.Gateway.Razor/Pages/GatewayMonitorPage/Variable/VariableRuntimeInfo.razor.cs +++ b/src/Gateway/ThingsGateway.Gateway.Razor/Pages/GatewayMonitorPage/Variable/VariableRuntimeInfo.razor.cs @@ -55,11 +55,11 @@ public partial class VariableRuntimeInfo : IDisposable scheduler = new SmartTriggerScheduler(Notify, TimeSpan.FromMilliseconds(1000)); #if !Management - _ = RunTimerAsync(); + //timer = new TimerX(RunTimerAsync, null, 1000, 1000) { Async = true }; #endif base.OnInitialized(); } - + //private TimerX timer; /// /// IntFormatter /// @@ -92,27 +92,23 @@ public partial class VariableRuntimeInfo : IDisposable await InvokeAsync(table.QueryAsync); } - private async Task RunTimerAsync() - { - while (!Disposed) - { - try - { - //if (table != null) - // await table.QueryAsync(); + //private async Task RunTimerAsync(object? state) + //{ + // try + // { + // //if (table != null) + // // await InvokeAsync(() => table.RowElementRefresh()); - await InvokeAsync(StateHasChanged); - } - catch (Exception ex) - { - NewLife.Log.XTrace.WriteException(ex); - } - finally - { - await Task.Delay(1000); - } - } - } + // await InvokeAsync(StateHasChanged); + // } + // catch (Exception ex) + // { + // NewLife.Log.XTrace.WriteException(ex); + // } + // finally + // { + // } + //} #region 查询 @@ -126,7 +122,7 @@ public partial class VariableRuntimeInfo : IDisposable return data; #else var data = Items - .WhereIf(!options.SearchText.IsNullOrWhiteSpace(), a => a.Name.Contains(options.SearchText)) + .WhereIf(!string.IsNullOrWhiteSpace(options.SearchText), a => a.Name.Contains(options.SearchText)) .GetQueryData(options); _option = options; return Task.FromResult(data); @@ -354,7 +350,7 @@ public partial class VariableRuntimeInfo : IDisposable #if !Management var models = Items - .WhereIf(!_option.SearchText.IsNullOrWhiteSpace(), a => a.Name.Contains(_option.SearchText)).GetData(_option, out var total).Cast().ToList(); + .WhereIf(!string.IsNullOrWhiteSpace(_option.SearchText), a => a.Name.Contains(_option.SearchText)).GetData(_option, out var total).Cast().ToList(); #else diff --git a/src/Plugin/ThingsGateway.Foundation.Modbus/Slave/ModbusSlave.cs b/src/Plugin/ThingsGateway.Foundation.Modbus/Slave/ModbusSlave.cs index 162593133..6472503a7 100644 --- a/src/Plugin/ThingsGateway.Foundation.Modbus/Slave/ModbusSlave.cs +++ b/src/Plugin/ThingsGateway.Foundation.Modbus/Slave/ModbusSlave.cs @@ -516,185 +516,169 @@ public class ModbusSlave : DeviceBase, IModbusAddress return new OperResult(ex); } } - - /// - protected override async Task ChannelReceived(IClientChannel client, ReceivedDataEventArgs e, bool last) + protected override Task ChannelReceived(IClientChannel client, ReceivedDataEventArgs e, bool last) { - var requestInfo = e.RequestInfo; - bool modbusRtu = false; - ModbusRequest modbusRequest = default; - ReadOnlySequence readOnlySequences = default; - //接收外部报文 - if (requestInfo is ModbusRtuSlaveMessage modbusRtuSlaveMessage) + return HandleChannelReceivedAsync(client, e, last); + } + + private async Task HandleChannelReceivedAsync(IClientChannel client, ReceivedDataEventArgs e, bool last) + { + if (!TryParseRequest(e.RequestInfo, out var modbusRequest, out var sequences, out var modbusRtu)) + return; + + if (!MulStation && modbusRequest.Station != Station) + return; + + var function = NormalizeFunctionCode(modbusRequest.FunctionCode); + + if (function <= 4) + await HandleReadRequestAsync(client, e, modbusRequest, sequences, modbusRtu).ConfigureAwait(false); + else + await HandleWriteRequestAsync(client, e, modbusRequest, sequences, modbusRtu, function).ConfigureAwait(false); + } + + private static bool TryParseRequest(object requestInfo, out ModbusRequest modbusRequest, out ReadOnlySequence sequences, out bool modbusRtu) + { + modbusRequest = default; + sequences = default; + modbusRtu = false; + + switch (requestInfo) { - if (!modbusRtuSlaveMessage.IsSuccess) - { - return; - } - modbusRequest = modbusRtuSlaveMessage.Request; - readOnlySequences = modbusRtuSlaveMessage.Sequences; - modbusRtu = true; + case ModbusRtuSlaveMessage rtuMsg when rtuMsg.IsSuccess: + modbusRequest = rtuMsg.Request; + sequences = rtuMsg.Sequences; + modbusRtu = true; + return true; + + case ModbusTcpSlaveMessage tcpMsg when tcpMsg.IsSuccess: + modbusRequest = tcpMsg.Request; + sequences = tcpMsg.Sequences; + modbusRtu = false; + return true; + + default: + return false; } - else if (requestInfo is ModbusTcpSlaveMessage modbusTcpSlaveMessage) + } + + private static byte NormalizeFunctionCode(byte funcCode) + => funcCode > 0x30 ? (byte)(funcCode - 0x30) : funcCode; + + private async Task HandleReadRequestAsync( + IClientChannel client, + ReceivedDataEventArgs e, + ModbusRequest modbusRequest, + ReadOnlySequence sequences, + bool modbusRtu) + { + var data = ModbusRequest(modbusRequest, true); + if (!data.IsSuccess) { - if (!modbusTcpSlaveMessage.IsSuccess) - { + await WriteError(modbusRtu, client, sequences, e).ConfigureAwait(false); + return; + } + + ValueByteBlock byteBlock = new(1024); + try + { + WriteReadResponse(modbusRequest, sequences, data.Content, ref byteBlock, modbusRtu); + await ReturnData(client, byteBlock.Memory, e).ConfigureAwait(false); + } + catch + { + await WriteError(modbusRtu, client, sequences, e).ConfigureAwait(false); + } + finally + { + byteBlock.SafeDispose(); + } + } + + private async Task HandleWriteRequestAsync( + IClientChannel client, + ReceivedDataEventArgs e, + ModbusRequest modbusRequest, + ReadOnlySequence sequences, + bool modbusRtu, + byte f) + { + var modbusAddress = new ModbusAddress(modbusRequest); + bool isSuccess; + + switch (f) + { + case 5: + case 15: + modbusAddress.WriteFunctionCode = modbusRequest.FunctionCode; + modbusAddress.FunctionCode = 1; + isSuccess = await HandleWriteCoreAsync(modbusAddress, client, modbusRequest).ConfigureAwait(false); + break; + + case 6: + case 16: + modbusAddress.WriteFunctionCode = modbusRequest.FunctionCode; + modbusAddress.FunctionCode = 3; + isSuccess = await HandleWriteCoreAsync(modbusAddress, client, modbusRequest).ConfigureAwait(false); + break; + + default: return; - } - modbusRequest = modbusTcpSlaveMessage.Request; - readOnlySequences = modbusTcpSlaveMessage.Sequences; - modbusRtu = false; + } + + if (isSuccess) + await WriteSuccess(modbusRtu, client, sequences, e).ConfigureAwait(false); + else + await WriteError(modbusRtu, client, sequences, e).ConfigureAwait(false); + } + + private async Task HandleWriteCoreAsync(ModbusAddress address, IClientChannel client, ModbusRequest modbusRequest) + { + if (WriteData != null) + { + var result = await WriteData(address, ThingsGatewayBitConverter, client).ConfigureAwait(false); + if (!result.IsSuccess) return false; + } + + if (IsWriteMemory) + { + var memResult = ModbusRequest(modbusRequest, false); + return memResult.IsSuccess; + } + + return true; + } + + private static void WriteReadResponse( + ModbusRequest modbusRequest, + ReadOnlySequence sequences, + ReadOnlyMemory content, + ref ValueByteBlock byteBlock, + bool modbusRtu) + { + if (modbusRtu) + ByteBlockExtension.Write(ref byteBlock, sequences.Slice(0, 2)); + else + ByteBlockExtension.Write(ref byteBlock, sequences.Slice(0, 8)); + + if (modbusRequest.IsBitFunction) + { + var bitdata = content.Span.ByteToBool().AsSpan().BoolArrayToByte(); + var len = (int)Math.Ceiling(modbusRequest.Length / 8.0); + var bitWriteData = bitdata.AsMemory().Slice(0, len); + WriterExtension.WriteValue(ref byteBlock, (byte)bitWriteData.Length); + byteBlock.Write(bitWriteData.Span); } else { - return; + WriterExtension.WriteValue(ref byteBlock, (byte)content.Length); + byteBlock.Write(content.Span); } - //忽略不同设备地址的报文 - if (!MulStation && modbusRequest.Station != Station) - { - return; - } - var f = modbusRequest.FunctionCode > 0x30 ? modbusRequest.FunctionCode - 0x30 : modbusRequest.FunctionCode; - if (f <= 4) - { - var data = ModbusRequest(modbusRequest, true); - if (data.IsSuccess) - { - ValueByteBlock byteBlock = new(1024); - try - { - if (modbusRtu) - { - ByteBlockExtension.Write(ref byteBlock, readOnlySequences.Slice(0, 2)); - if (modbusRequest.IsBitFunction) - { - var bitdata = data.Content.Span.ByteToBool().AsSpan().BoolArrayToByte(); - ReadOnlyMemory bitwritedata = bitdata.Length == (int)Math.Ceiling(modbusRequest.Length / 8.0) ? bitdata.AsMemory() : bitdata.AsMemory().Slice(0, (int)Math.Ceiling(modbusRequest.Length / 8.0)); - WriterExtension.WriteValue(ref byteBlock, (byte)bitwritedata.Length); - byteBlock.Write(bitwritedata.Span); - } - else - { - WriterExtension.WriteValue(ref byteBlock, (byte)data.Content.Length); - byteBlock.Write(data.Content.Span); - } - byteBlock.Write(CRC16Utils.Crc16Only(byteBlock.Memory.Span)); - await ReturnData(client, byteBlock.Memory, e).ConfigureAwait(false); - } - else - { - ByteBlockExtension.Write(ref byteBlock, readOnlySequences.Slice(0, 8)); - if (modbusRequest.IsBitFunction) - { - var bitdata = data.Content.Span.ByteToBool().AsSpan().BoolArrayToByte(); - ReadOnlyMemory bitwritedata = bitdata.Length == (int)Math.Ceiling(modbusRequest.Length / 8.0) ? bitdata.AsMemory() : bitdata.AsMemory().Slice(0, (int)Math.Ceiling(modbusRequest.Length / 8.0)); - WriterExtension.WriteValue(ref byteBlock, (byte)bitwritedata.Length); - byteBlock.Write(bitwritedata.Span); - } - else - { - WriterExtension.WriteValue(ref byteBlock, (byte)data.Content.Length); - byteBlock.Write(data.Content.Span); - } - ByteBlockExtension.WriteBackValue(ref byteBlock, (byte)(byteBlock.Length - 6), EndianType.Big, 5); - await ReturnData(client, byteBlock.Memory, e).ConfigureAwait(false); - } - } - catch - { - await WriteError(modbusRtu, client, readOnlySequences, e).ConfigureAwait(false); - } - finally - { - byteBlock.SafeDispose(); - } - } - else - { - await WriteError(modbusRtu, client, readOnlySequences, e).ConfigureAwait(false);//返回错误码 - } - } - else//写入 - { - if (f == 5 || f == 15) - { - //写入继电器 - if (WriteData != null) - { - var modbusAddress = new ModbusAddress(modbusRequest) { WriteFunctionCode = modbusRequest.FunctionCode, FunctionCode = 1 }; - // 接收外部写入时,传出变量地址/写入字节组/转换规则/客户端 - if ((await WriteData(modbusAddress, ThingsGatewayBitConverter, client).ConfigureAwait(false)).IsSuccess) - { - await WriteSuccess(modbusRtu, client, readOnlySequences, e).ConfigureAwait(false); - if (IsWriteMemory) - { - var result = ModbusRequest(modbusRequest, false); - if (result.IsSuccess) - await WriteSuccess(modbusRtu, client, readOnlySequences, e).ConfigureAwait(false); - else - await WriteError(modbusRtu, client, readOnlySequences, e).ConfigureAwait(false); - } - else - await WriteSuccess(modbusRtu, client, readOnlySequences, e).ConfigureAwait(false); - } - else - { - await WriteError(modbusRtu, client, readOnlySequences, e).ConfigureAwait(false); - } - } - else - { - //写入内存区 - var result = ModbusRequest(modbusRequest, false); - if (result.IsSuccess) - { - await WriteSuccess(modbusRtu, client, readOnlySequences, e).ConfigureAwait(false); - } - else - { - await WriteError(modbusRtu, client, readOnlySequences, e).ConfigureAwait(false); - } - } - } - else if (f == 6 || f == 16) - { - //写入寄存器 - if (WriteData != null) - { - var modbusAddress = new ModbusAddress(modbusRequest) { WriteFunctionCode = modbusRequest.FunctionCode, FunctionCode = 3 }; - if ((await WriteData(modbusAddress, ThingsGatewayBitConverter, client).ConfigureAwait(false)).IsSuccess) - { - if (IsWriteMemory) - { - var result = ModbusRequest(modbusRequest, false); - if (result.IsSuccess) - await WriteSuccess(modbusRtu, client, readOnlySequences, e).ConfigureAwait(false); - else - await WriteError(modbusRtu, client, readOnlySequences, e).ConfigureAwait(false); - } - else - await WriteSuccess(modbusRtu, client, readOnlySequences, e).ConfigureAwait(false); - } - else - { - await WriteError(modbusRtu, client, readOnlySequences, e).ConfigureAwait(false); - } - } - else - { - var result = ModbusRequest(modbusRequest, false); - if (result.IsSuccess) - { - await WriteSuccess(modbusRtu, client, readOnlySequences, e).ConfigureAwait(false); - } - else - { - await WriteError(modbusRtu, client, readOnlySequences, e).ConfigureAwait(false); - } - } - } - } + if (modbusRtu) + byteBlock.Write(CRC16Utils.Crc16Only(byteBlock.Memory.Span)); + else + ByteBlockExtension.WriteBackValue(ref byteBlock, (byte)(byteBlock.Length - 6), EndianType.Big, 5); } private async Task ReturnData(IClientChannel client, ReadOnlyMemory sendData, ReceivedDataEventArgs e)