build: 10.11.98

This commit is contained in:
2248356998 qq.com
2025-10-13 21:10:39 +08:00
parent 628b51a353
commit 301beda2a2
9 changed files with 297 additions and 222 deletions

View File

@@ -20,7 +20,7 @@
<ItemGroup> <ItemGroup>
<PackageReference Include="Riok.Mapperly" Version="4.2.1" ExcludeAssets="runtime" PrivateAssets="all" /> <PackageReference Include="Riok.Mapperly" Version="4.2.1" ExcludeAssets="runtime" PrivateAssets="all" />
<PackageReference Include="Rougamo.Fody" Version="5.0.1" /> <PackageReference Include="Rougamo.Fody" Version="5.0.2" />
</ItemGroup> </ItemGroup>
<ItemGroup Condition=" '$(TargetFramework)' == 'net8.0' "> <ItemGroup Condition=" '$(TargetFramework)' == 'net8.0' ">
<PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" Version="8.0.1" /> <PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" Version="8.0.1" />

View File

@@ -5,30 +5,101 @@ namespace ThingsGateway.NewLife;
public class ExpiringDictionary<TKey, TValue> : IDisposable public class ExpiringDictionary<TKey, TValue> : IDisposable
{ {
private ConcurrentDictionary<TKey, TValue> _dict = new(); /// <summary>缓存项</summary>
private readonly TimerX _cleanupTimer; public class CacheItem
public ExpiringDictionary(int cleanupInterval = 60000)
{ {
_cleanupTimer = new TimerX(Clear, null, cleanupInterval, cleanupInterval) { Async = true }; private TValue? _value;
/// <summary>数值</summary>
public TValue? Value { get => _value; }
/// <summary>过期时间。系统启动以来的毫秒数</summary>
public Int64 ExpiredTime { get; set; }
/// <summary>是否过期</summary>
public Boolean Expired => ExpiredTime <= Runtime.TickCount64;
/// <summary>访问时间</summary>
public Int64 VisitTime { get; private set; }
/// <summary>构造缓存项</summary>
/// <param name="value"></param>
/// <param name="expire"></param>
public CacheItem(TValue? value, Int32 expire) => Set(value, expire);
/// <summary>设置数值和过期时间</summary>
/// <param name="value"></param>
/// <param name="expire">过期时间,秒</param>
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;
}
/// <summary>更新访问时间并返回数值</summary>
/// <returns></returns>
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<TKey, CacheItem> _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) 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<TKey, TValue> func) public TValue GetOrAdd(TKey key, Func<TKey, TValue> func)
{ {
return _dict.GetOrAdd(key, func); CacheItem? item = null;
} do
public TValue GetOrAdd(TKey key, TValue value) {
{ if (_dict.TryGetValue(key, out item) && item != null) return item.Visit();
return _dict.GetOrAdd(key, value);
item ??= new CacheItem(func(key), defaultExpire);
}
while (!_dict.TryAdd(key, item));
return item.Visit();
} }
public bool TryRemove(TKey key) => _dict.TryRemove(key, out _); public bool TryRemove(TKey key) => _dict.TryRemove(key, out _);
@@ -41,7 +112,31 @@ public class ExpiringDictionary<TKey, TValue> : IDisposable
_dict = new(); _dict = new();
data.Clear(); data.Clear();
} }
private void TimerClear(object? state)
{
var dic = _dict;
if (dic.IsEmpty) return;
// 60分钟之内过期的数据进入LRU淘汰
var now = Runtime.TickCount64;
// 这里先计算,性能很重要
var toDels = new List<TKey>();
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() public void Dispose()
{ {
_dict.Clear(); _dict.Clear();

View File

@@ -561,7 +561,7 @@ public static class Reflect
/// <param name="method"></param> /// <param name="method"></param>
/// <param name="target"></param> /// <param name="target"></param>
/// <returns></returns> /// <returns></returns>
public static TFunc? As<TFunc>(this MethodInfo method, object? target = null) public static TFunc? As<TFunc>(this MethodInfo method, object? target = null) where TFunc : class
{ {
if (method == null) return default; if (method == null) return default;
@@ -569,10 +569,14 @@ public static class Reflect
var func = DelegateCache<TFunc>.Cache.GetOrAdd( var func = DelegateCache<TFunc>.Cache.GetOrAdd(
key, key,
_ => (TFunc)(object)( _ =>
{
return (
target == null target == null
? Delegate.CreateDelegate(typeof(TFunc), method, true) ? Delegate.CreateDelegate(typeof(TFunc), method, true) as TFunc
: Delegate.CreateDelegate(typeof(TFunc), target, method, true))); : Delegate.CreateDelegate(typeof(TFunc), target, method, true) as TFunc
);
});
return func; return func;
} }

View File

@@ -391,11 +391,7 @@ public class TimerX : ITimer, ITimerx, IDisposable
// 释放非托管资源 // 释放非托管资源
Scheduler?.Remove(this, disposing ? "Dispose" : "GC"); Scheduler?.Remove(this, disposing ? "Dispose" : "GC");
DelegateCache<TimerCallback>.Cache.Clear();
#if NET6_0_OR_GREATER
DelegateCache<Func<Object?, ValueTask>>.Cache.Clear();
#endif
DelegateCache<Func<Object?, Task>>.Cache.Clear();
} }

View File

@@ -1,9 +1,9 @@
<Project> <Project>
<PropertyGroup> <PropertyGroup>
<PluginVersion>10.11.97</PluginVersion> <PluginVersion>10.11.98</PluginVersion>
<ProPluginVersion>10.11.97</ProPluginVersion> <ProPluginVersion>10.11.98</ProPluginVersion>
<DefaultVersion>10.11.97</DefaultVersion> <DefaultVersion>10.11.98</DefaultVersion>
<AuthenticationVersion>10.11.6</AuthenticationVersion> <AuthenticationVersion>10.11.6</AuthenticationVersion>
<SourceGeneratorVersion>10.11.6</SourceGeneratorVersion> <SourceGeneratorVersion>10.11.6</SourceGeneratorVersion>
<NET8Version>8.0.20</NET8Version> <NET8Version>8.0.20</NET8Version>
@@ -12,7 +12,7 @@
<IsTrimmable>false</IsTrimmable> <IsTrimmable>false</IsTrimmable>
<ManagementProPluginVersion>10.11.87</ManagementProPluginVersion> <ManagementProPluginVersion>10.11.87</ManagementProPluginVersion>
<ManagementPluginVersion>10.11.87</ManagementPluginVersion> <ManagementPluginVersion>10.11.87</ManagementPluginVersion>
<TSVersion>4.0.0-beta.115</TSVersion> <TSVersion>4.0.0-beta.120</TSVersion>
</PropertyGroup> </PropertyGroup>

View File

@@ -9,7 +9,7 @@
<ItemGroup> <ItemGroup>
<PackageReference Include="Portable.BouncyCastle" Version="1.9.0" /> <PackageReference Include="Portable.BouncyCastle" Version="1.9.0" />
<PackageReference Include="Riok.Mapperly" Version="4.2.1" ExcludeAssets="runtime" PrivateAssets="all" /> <PackageReference Include="Riok.Mapperly" Version="4.2.1" ExcludeAssets="runtime" PrivateAssets="all" />
<PackageReference Include="Rougamo.Fody" Version="5.0.1" /> <PackageReference Include="Rougamo.Fody" Version="5.0.2" />
<PackageReference Include="TouchSocket.Dmtp" Version="$(TSVersion)" /> <PackageReference Include="TouchSocket.Dmtp" Version="$(TSVersion)" />
<!--<PackageReference Include="TouchSocket.WebApi.Swagger" Version="$(TSVersion)" />--> <!--<PackageReference Include="TouchSocket.WebApi.Swagger" Version="$(TSVersion)" />-->
<PackageReference Include="TouchSocket.WebApi" Version="$(TSVersion)" /> <PackageReference Include="TouchSocket.WebApi" Version="$(TSVersion)" />

View File

@@ -21,7 +21,7 @@
ShowToolbar="true" ShowToolbar="true"
ShowExportButton ShowExportButton
IsAutoRefresh IsAutoRefresh
AutoRefreshInterval="2000" AutoRefreshInterval="1000"
ShowDefaultButtons=true ShowDefaultButtons=true
ShowSearch=false ShowSearch=false
ExtendButtonColumnWidth=220 ExtendButtonColumnWidth=220

View File

@@ -55,11 +55,11 @@ public partial class VariableRuntimeInfo : IDisposable
scheduler = new SmartTriggerScheduler(Notify, TimeSpan.FromMilliseconds(1000)); scheduler = new SmartTriggerScheduler(Notify, TimeSpan.FromMilliseconds(1000));
#if !Management #if !Management
_ = RunTimerAsync(); //timer = new TimerX(RunTimerAsync, null, 1000, 1000) { Async = true };
#endif #endif
base.OnInitialized(); base.OnInitialized();
} }
//private TimerX timer;
/// <summary> /// <summary>
/// IntFormatter /// IntFormatter
/// </summary> /// </summary>
@@ -92,27 +92,23 @@ public partial class VariableRuntimeInfo : IDisposable
await InvokeAsync(table.QueryAsync); await InvokeAsync(table.QueryAsync);
} }
private async Task RunTimerAsync() //private async Task RunTimerAsync(object? state)
{ //{
while (!Disposed) // try
{ // {
try // //if (table != null)
{ // // await InvokeAsync(() => table.RowElementRefresh());
//if (table != null)
// await table.QueryAsync();
await InvokeAsync(StateHasChanged); // await InvokeAsync(StateHasChanged);
} // }
catch (Exception ex) // catch (Exception ex)
{ // {
NewLife.Log.XTrace.WriteException(ex); // NewLife.Log.XTrace.WriteException(ex);
} // }
finally // finally
{ // {
await Task.Delay(1000); // }
} //}
}
}
#region #region
@@ -126,7 +122,7 @@ public partial class VariableRuntimeInfo : IDisposable
return data; return data;
#else #else
var data = Items 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); .GetQueryData(options);
_option = options; _option = options;
return Task.FromResult(data); return Task.FromResult(data);
@@ -354,7 +350,7 @@ public partial class VariableRuntimeInfo : IDisposable
#if !Management #if !Management
var models = Items var models = Items
.WhereIf(!_option.SearchText.IsNullOrWhiteSpace(), a => a.Name.Contains(_option.SearchText)).GetData(_option, out var total).Cast<Variable>().ToList(); .WhereIf(!string.IsNullOrWhiteSpace(_option.SearchText), a => a.Name.Contains(_option.SearchText)).GetData(_option, out var total).Cast<Variable>().ToList();
#else #else

View File

@@ -516,185 +516,169 @@ public class ModbusSlave : DeviceBase, IModbusAddress
return new OperResult(ex); return new OperResult(ex);
} }
} }
protected override Task ChannelReceived(IClientChannel client, ReceivedDataEventArgs e, bool last)
/// <inheritdoc/>
protected override async Task ChannelReceived(IClientChannel client, ReceivedDataEventArgs e, bool last)
{ {
var requestInfo = e.RequestInfo; return HandleChannelReceivedAsync(client, e, last);
bool modbusRtu = false; }
ModbusRequest modbusRequest = default;
ReadOnlySequence<byte> readOnlySequences = default; private async Task HandleChannelReceivedAsync(IClientChannel client, ReceivedDataEventArgs e, bool last)
//接收外部报文 {
if (requestInfo is ModbusRtuSlaveMessage modbusRtuSlaveMessage) 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<byte> sequences, out bool modbusRtu)
{
modbusRequest = default;
sequences = default;
modbusRtu = false;
switch (requestInfo)
{ {
if (!modbusRtuSlaveMessage.IsSuccess) case ModbusRtuSlaveMessage rtuMsg when rtuMsg.IsSuccess:
{ modbusRequest = rtuMsg.Request;
return; sequences = rtuMsg.Sequences;
} modbusRtu = true;
modbusRequest = modbusRtuSlaveMessage.Request; return true;
readOnlySequences = modbusRtuSlaveMessage.Sequences;
modbusRtu = 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<byte> 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<byte> 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; return;
} }
modbusRequest = modbusTcpSlaveMessage.Request;
readOnlySequences = modbusTcpSlaveMessage.Sequences; if (isSuccess)
modbusRtu = false; await WriteSuccess(modbusRtu, client, sequences, e).ConfigureAwait(false);
else
await WriteError(modbusRtu, client, sequences, e).ConfigureAwait(false);
}
private async Task<bool> 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<byte> sequences,
ReadOnlyMemory<byte> 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 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) if (modbusRtu)
{ byteBlock.Write(CRC16Utils.Crc16Only(byteBlock.Memory.Span));
var data = ModbusRequest(modbusRequest, true); else
if (data.IsSuccess) ByteBlockExtension.WriteBackValue(ref byteBlock, (byte)(byteBlock.Length - 6), EndianType.Big, 5);
{
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<byte> 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<byte> 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);
}
}
}
}
} }
private async Task ReturnData(IClientChannel client, ReadOnlyMemory<byte> sendData, ReceivedDataEventArgs e) private async Task ReturnData(IClientChannel client, ReadOnlyMemory<byte> sendData, ReceivedDataEventArgs e)