Compare commits
14 Commits
21215d0379
...
10.11.118.
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6090108597 | ||
|
|
b47b9e6f43 | ||
|
|
18d1cffb2d | ||
|
|
516fd7f235 | ||
|
|
2ee16c3533 | ||
|
|
7d22f5c78e | ||
|
|
3e604ee2fd | ||
|
|
47e442874c | ||
|
|
2a8c0cbab1 | ||
|
|
c26898b49d | ||
|
|
00c24d06a3 | ||
|
|
3461f34240 | ||
|
|
aa1ce08c02 | ||
|
|
9c230c2da9 |
@@ -15,11 +15,11 @@ public partial class AdminTable<TItem> where TItem : class, new()
|
|||||||
{
|
{
|
||||||
/// <inheritdoc cref="Table{TItem}.OnColumnVisibleChanged"/>
|
/// <inheritdoc cref="Table{TItem}.OnColumnVisibleChanged"/>
|
||||||
[Parameter]
|
[Parameter]
|
||||||
public Func<string,bool, Task> OnColumnVisibleChanged { get; set; }
|
public Func<string, bool, Task> OnColumnVisibleChanged { get; set; }
|
||||||
|
|
||||||
/// <inheritdoc cref="Table{TItem}.OnColumnCreating"/>
|
/// <inheritdoc cref="Table{TItem}.OnColumnCreating"/>
|
||||||
[Parameter]
|
[Parameter]
|
||||||
public Func<List<ITableColumn>,Task> OnColumnCreating { get; set; }
|
public Func<List<ITableColumn>, Task> OnColumnCreating { get; set; }
|
||||||
/// <inheritdoc cref="Table{TItem}.RenderMode"/>
|
/// <inheritdoc cref="Table{TItem}.RenderMode"/>
|
||||||
[Parameter]
|
[Parameter]
|
||||||
public TableRenderMode RenderMode { get; set; }
|
public TableRenderMode RenderMode { get; set; }
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ internal class CacheManager
|
|||||||
{
|
{
|
||||||
private IMemoryCache Cache { get; set; }
|
private IMemoryCache Cache { get; set; }
|
||||||
|
|
||||||
private IServiceProvider Provider { get; set; }
|
private static IServiceProvider Provider => App.RootServices;
|
||||||
|
|
||||||
[NotNull]
|
[NotNull]
|
||||||
private static CacheManager? Instance { get; set; }
|
private static CacheManager? Instance { get; set; }
|
||||||
@@ -40,8 +40,7 @@ internal class CacheManager
|
|||||||
static CacheManager()
|
static CacheManager()
|
||||||
{
|
{
|
||||||
Instance = new();
|
Instance = new();
|
||||||
Instance.Provider = App.RootServices;
|
Instance.Cache = Provider.GetRequiredService<IMemoryCache>();
|
||||||
Instance.Cache = Instance.Provider.GetRequiredService<IMemoryCache>();
|
|
||||||
Options = App.RootServices.GetRequiredService<IOptions<BootstrapBlazorOptions>>().Value;
|
Options = App.RootServices.GetRequiredService<IOptions<BootstrapBlazorOptions>>().Value;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -236,7 +235,7 @@ internal class CacheManager
|
|||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
public static IStringLocalizer? CreateLocalizerByType(Type resourceSource) => resourceSource.Assembly.IsDynamic
|
public static IStringLocalizer? CreateLocalizerByType(Type resourceSource) => resourceSource.Assembly.IsDynamic
|
||||||
? null
|
? null
|
||||||
: Instance.Provider.GetRequiredService<IStringLocalizerFactory>().Create(resourceSource);
|
: Provider.GetRequiredService<IStringLocalizerFactory>().Create(resourceSource);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 获得 <see cref="JsonLocalizationOptions"/> 值
|
/// 获得 <see cref="JsonLocalizationOptions"/> 值
|
||||||
@@ -244,7 +243,7 @@ internal class CacheManager
|
|||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
private static JsonLocalizationOptions GetJsonLocalizationOption()
|
private static JsonLocalizationOptions GetJsonLocalizationOption()
|
||||||
{
|
{
|
||||||
var localizationOptions = Instance.Provider.GetRequiredService<IOptions<JsonLocalizationOptions>>();
|
var localizationOptions = Provider.GetRequiredService<IOptions<JsonLocalizationOptions>>();
|
||||||
return localizationOptions.Value;
|
return localizationOptions.Value;
|
||||||
}
|
}
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -253,7 +252,7 @@ internal class CacheManager
|
|||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
private static BootstrapBlazorOptions GetBootstrapBlazorOption()
|
private static BootstrapBlazorOptions GetBootstrapBlazorOption()
|
||||||
{
|
{
|
||||||
var localizationOptions = Instance.Provider.GetRequiredService<IOptions<BootstrapBlazorOptions>>();
|
var localizationOptions = Provider.GetRequiredService<IOptions<BootstrapBlazorOptions>>();
|
||||||
return localizationOptions.Value;
|
return localizationOptions.Value;
|
||||||
}
|
}
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -269,7 +268,7 @@ internal class CacheManager
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
IStringLocalizer? ret = null;
|
IStringLocalizer? ret = null;
|
||||||
var factories = Instance.Provider.GetServices<IStringLocalizerFactory>();
|
var factories = Provider.GetServices<IStringLocalizerFactory>();
|
||||||
var factory = factories.LastOrDefault(a => a is not JsonStringLocalizerFactory);
|
var factory = factories.LastOrDefault(a => a is not JsonStringLocalizerFactory);
|
||||||
if (factory != null)
|
if (factory != null)
|
||||||
{
|
{
|
||||||
@@ -345,7 +344,7 @@ internal class CacheManager
|
|||||||
/// <param name="typeName"></param>
|
/// <param name="typeName"></param>
|
||||||
/// <param name="includeParentCultures"></param>
|
/// <param name="includeParentCultures"></param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
public static IEnumerable<LocalizedString> GetTypeStringsFromResolve(string typeName, bool includeParentCultures = true) => Instance.Provider.GetRequiredService<ILocalizationResolve>().GetAllStringsByType(typeName, includeParentCultures);
|
public static IEnumerable<LocalizedString> GetTypeStringsFromResolve(string typeName, bool includeParentCultures = true) => Provider.GetRequiredService<ILocalizationResolve>().GetAllStringsByType(typeName, includeParentCultures);
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
#region DisplayName
|
#region DisplayName
|
||||||
|
|||||||
@@ -4,7 +4,6 @@
|
|||||||
|
|
||||||
#nullable disable
|
#nullable disable
|
||||||
|
|
||||||
using System.Collections;
|
|
||||||
using System.Runtime.CompilerServices;
|
using System.Runtime.CompilerServices;
|
||||||
|
|
||||||
namespace System.Collections.Concurrent
|
namespace System.Collections.Concurrent
|
||||||
|
|||||||
@@ -5,12 +5,10 @@
|
|||||||
#nullable disable
|
#nullable disable
|
||||||
#pragma warning disable CS8632 // The annotation for nullable reference types should only be used in code within a '#nullable' annotations context.
|
#pragma warning disable CS8632 // The annotation for nullable reference types should only be used in code within a '#nullable' annotations context.
|
||||||
|
|
||||||
using System.Collections;
|
|
||||||
using System.Collections.ObjectModel;
|
using System.Collections.ObjectModel;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.Diagnostics.CodeAnalysis;
|
using System.Diagnostics.CodeAnalysis;
|
||||||
using System.Runtime.CompilerServices;
|
using System.Runtime.CompilerServices;
|
||||||
using System.Collections.Concurrent;
|
|
||||||
|
|
||||||
using static System.Collections.Concurrent.DictionaryImpl;
|
using static System.Collections.Concurrent.DictionaryImpl;
|
||||||
|
|
||||||
|
|||||||
@@ -31,8 +31,8 @@ public class ObjectPool<T> : DisposeBase, IPool<T> where T : notnull
|
|||||||
/// <summary>最小个数。默认1</summary>
|
/// <summary>最小个数。默认1</summary>
|
||||||
public Int32 Min { get; set; } = 1;
|
public Int32 Min { get; set; } = 1;
|
||||||
|
|
||||||
/// <summary>空闲清理时间。最小个数之上的资源超过空闲时间时被清理,默认10s</summary>
|
/// <summary>空闲清理时间。最小个数之上的资源超过空闲时间时被清理,默认60s</summary>
|
||||||
public Int32 IdleTime { get; set; } = 10;
|
public Int32 IdleTime { get; set; } = 60;
|
||||||
|
|
||||||
/// <summary>完全空闲清理时间。最小个数之下的资源超过空闲时间时被清理,默认0s永不清理</summary>
|
/// <summary>完全空闲清理时间。最小个数之下的资源超过空闲时间时被清理,默认0s永不清理</summary>
|
||||||
public Int32 AllIdleTime { get; set; } = 0;
|
public Int32 AllIdleTime { get; set; } = 0;
|
||||||
@@ -126,9 +126,7 @@ public class ObjectPool<T> : DisposeBase, IPool<T> where T : notnull
|
|||||||
if (Max > 0 && count >= Max)
|
if (Max > 0 && count >= Max)
|
||||||
{
|
{
|
||||||
var msg = $"申请失败,已有 {count:n0} 达到或超过最大值 {Max:n0}";
|
var msg = $"申请失败,已有 {count:n0} 达到或超过最大值 {Max:n0}";
|
||||||
|
|
||||||
WriteLog("Acquire Max " + msg);
|
WriteLog("Acquire Max " + msg);
|
||||||
|
|
||||||
throw new Exception(Name + " " + msg);
|
throw new Exception(Name + " " + msg);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -268,7 +266,7 @@ public class ObjectPool<T> : DisposeBase, IPool<T> where T : notnull
|
|||||||
{
|
{
|
||||||
if (_timer != null) return;
|
if (_timer != null) return;
|
||||||
|
|
||||||
_timer = new TimerX(Work, null, 5000, 5000) { Async = true };
|
_timer = new TimerX(Work, null, 60000, 60000) { Async = true };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -281,7 +279,7 @@ public class ObjectPool<T> : DisposeBase, IPool<T> where T : notnull
|
|||||||
var count = 0;
|
var count = 0;
|
||||||
|
|
||||||
// 清理过期不还。避免有借没还
|
// 清理过期不还。避免有借没还
|
||||||
if (!_busy.IsEmpty)
|
if (AllIdleTime > 0 && !_busy.IsEmpty)
|
||||||
{
|
{
|
||||||
var exp = TimerX.Now.AddSeconds(-AllIdleTime);
|
var exp = TimerX.Now.AddSeconds(-AllIdleTime);
|
||||||
foreach (var item in _busy)
|
foreach (var item in _busy)
|
||||||
|
|||||||
241
src/Admin/ThingsGateway.NewLife.X/Collections/ObjectPoolLock.cs
Normal file
241
src/Admin/ThingsGateway.NewLife.X/Collections/ObjectPoolLock.cs
Normal file
@@ -0,0 +1,241 @@
|
|||||||
|
using ThingsGateway.NewLife.Log;
|
||||||
|
using ThingsGateway.NewLife.Reflection;
|
||||||
|
|
||||||
|
namespace ThingsGateway.NewLife.Collections;
|
||||||
|
|
||||||
|
/// <summary>资源池。支持空闲释放,主要用于数据库连接池和网络连接池</summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// 文档 https://newlifex.com/core/object_pool
|
||||||
|
/// </remarks>
|
||||||
|
/// <typeparam name="T"></typeparam>
|
||||||
|
public class ObjectPoolLock<T> : DisposeBase, IPool<T> where T : class
|
||||||
|
{
|
||||||
|
#region 属性
|
||||||
|
/// <summary>名称</summary>
|
||||||
|
public String Name { get; set; }
|
||||||
|
|
||||||
|
private Int32 _FreeCount;
|
||||||
|
/// <summary>空闲个数</summary>
|
||||||
|
public Int32 FreeCount => _FreeCount;
|
||||||
|
|
||||||
|
private Int32 _BusyCount;
|
||||||
|
/// <summary>繁忙个数</summary>
|
||||||
|
public Int32 BusyCount => _BusyCount;
|
||||||
|
|
||||||
|
/// <summary>最大个数。默认0,0表示无上限</summary>
|
||||||
|
public Int32 Max { get; set; } = 0;
|
||||||
|
|
||||||
|
private readonly object _syncRoot = new();
|
||||||
|
|
||||||
|
/// <summary>基础空闲集合。只保存最小个数,最热部分</summary>
|
||||||
|
private readonly Stack<T> _free = new();
|
||||||
|
|
||||||
|
/// <summary>借出去的放在这</summary>
|
||||||
|
private readonly HashSet<T> _busy = new();
|
||||||
|
|
||||||
|
//private readonly Object SyncRoot = new();
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region 构造
|
||||||
|
/// <summary>实例化一个资源池</summary>
|
||||||
|
public ObjectPoolLock()
|
||||||
|
{
|
||||||
|
var str = GetType().Name;
|
||||||
|
if (str.Contains('`')) str = str.Substring(null, "`");
|
||||||
|
if (str != "Pool")
|
||||||
|
Name = str;
|
||||||
|
else
|
||||||
|
Name = $"Pool<{typeof(T).Name}>";
|
||||||
|
|
||||||
|
}
|
||||||
|
~ObjectPoolLock()
|
||||||
|
{
|
||||||
|
this.TryDispose();
|
||||||
|
}
|
||||||
|
/// <summary>销毁</summary>
|
||||||
|
/// <param name="disposing"></param>
|
||||||
|
protected override void Dispose(Boolean disposing)
|
||||||
|
{
|
||||||
|
base.Dispose(disposing);
|
||||||
|
|
||||||
|
WriteLog($"Dispose {typeof(T).FullName} FreeCount={FreeCount:n0} BusyCount={BusyCount:n0}");
|
||||||
|
|
||||||
|
Clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
private volatile Boolean _inited;
|
||||||
|
private void Init()
|
||||||
|
{
|
||||||
|
if (_inited) return;
|
||||||
|
|
||||||
|
lock (lockThis)
|
||||||
|
{
|
||||||
|
if (_inited) return;
|
||||||
|
_inited = true;
|
||||||
|
|
||||||
|
WriteLog($"Init {typeof(T).FullName} Max={Max}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region 主方法
|
||||||
|
/// <summary>借出</summary>
|
||||||
|
/// <returns></returns>
|
||||||
|
public virtual T Get()
|
||||||
|
{
|
||||||
|
T? pi = null;
|
||||||
|
do
|
||||||
|
{
|
||||||
|
lock (_syncRoot)
|
||||||
|
{
|
||||||
|
if (_free.Count > 0)
|
||||||
|
{
|
||||||
|
pi = _free.Pop();
|
||||||
|
_FreeCount--;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (Max > 0 && BusyCount >= Max)
|
||||||
|
{
|
||||||
|
var msg = $"申请失败,已有 {BusyCount:n0} 达到或超过最大值 {Max:n0}";
|
||||||
|
WriteLog("Acquire Max " + msg);
|
||||||
|
throw new Exception(Name + " " + msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
pi = OnCreate();
|
||||||
|
if (BusyCount == 0) Init();
|
||||||
|
|
||||||
|
#if DEBUG
|
||||||
|
WriteLog("Acquire Create Free={0} Busy={1}", FreeCount, BusyCount + 1);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果拿到的对象不可用,则重新借
|
||||||
|
} while (pi == null || !OnGet(pi));
|
||||||
|
|
||||||
|
lock (_syncRoot)
|
||||||
|
{
|
||||||
|
// 加入繁忙集合
|
||||||
|
_busy.Add(pi);
|
||||||
|
|
||||||
|
_BusyCount++;
|
||||||
|
}
|
||||||
|
return pi;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>借出时是否可用</summary>
|
||||||
|
/// <param name="value"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
protected virtual Boolean OnGet(T value) => true;
|
||||||
|
|
||||||
|
/// <summary>申请资源包装项,Dispose时自动归还到池中</summary>
|
||||||
|
/// <returns></returns>
|
||||||
|
public PoolItem<T> GetItem() => new(this, Get());
|
||||||
|
|
||||||
|
/// <summary>归还</summary>
|
||||||
|
/// <param name="value"></param>
|
||||||
|
public virtual Boolean Return(T value)
|
||||||
|
{
|
||||||
|
if (value == null) return false;
|
||||||
|
lock (_syncRoot)
|
||||||
|
{
|
||||||
|
// 从繁忙队列找到并移除缓存项
|
||||||
|
if (!_busy.Remove(value))
|
||||||
|
{
|
||||||
|
#if DEBUG
|
||||||
|
WriteLog("Return Error");
|
||||||
|
#endif
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
_BusyCount--;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 是否可用
|
||||||
|
if (!OnReturn(value))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (value is DisposeBase db && db.Disposed)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
lock (_syncRoot)
|
||||||
|
{
|
||||||
|
_free.Push(value);
|
||||||
|
_FreeCount++;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>归还时是否可用</summary>
|
||||||
|
/// <param name="value"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
protected virtual Boolean OnReturn(T value) => true;
|
||||||
|
|
||||||
|
/// <summary>清空已有对象</summary>
|
||||||
|
public virtual Int32 Clear()
|
||||||
|
{
|
||||||
|
var count = _FreeCount + _BusyCount;
|
||||||
|
|
||||||
|
//_busy.Clear();
|
||||||
|
//_BusyCount = 0;
|
||||||
|
|
||||||
|
//_free.Clear();
|
||||||
|
//while (_free2.TryDequeue(out var rs)) ;
|
||||||
|
//_FreeCount = 0;
|
||||||
|
|
||||||
|
lock (_syncRoot)
|
||||||
|
{
|
||||||
|
count = _FreeCount + _BusyCount;
|
||||||
|
|
||||||
|
while (_free.Count > 0)
|
||||||
|
{
|
||||||
|
var pi = _free.Pop();
|
||||||
|
OnDispose(pi);
|
||||||
|
}
|
||||||
|
|
||||||
|
_FreeCount = 0;
|
||||||
|
|
||||||
|
foreach (var item in _busy)
|
||||||
|
{
|
||||||
|
OnDispose(item);
|
||||||
|
}
|
||||||
|
_busy.Clear();
|
||||||
|
_BusyCount = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>销毁</summary>
|
||||||
|
/// <param name="value"></param>
|
||||||
|
protected virtual void OnDispose(T? value) => value.TryDispose();
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region 重载
|
||||||
|
/// <summary>创建实例</summary>
|
||||||
|
/// <returns></returns>
|
||||||
|
protected virtual T? OnCreate() => (T?)typeof(T).CreateInstance();
|
||||||
|
#endregion
|
||||||
|
protected object lockThis = new();
|
||||||
|
|
||||||
|
#region 日志
|
||||||
|
/// <summary>日志</summary>
|
||||||
|
public ILog Log { get; set; } = Logger.Null;
|
||||||
|
|
||||||
|
/// <summary>写日志</summary>
|
||||||
|
/// <param name="format"></param>
|
||||||
|
/// <param name="args"></param>
|
||||||
|
public void WriteLog(String format, params Object?[] args)
|
||||||
|
{
|
||||||
|
if (Log?.Enable != true) return;
|
||||||
|
|
||||||
|
Log.Info(Name + "." + format, args);
|
||||||
|
}
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
namespace ThingsGateway.NewLife.Collections;
|
||||||
|
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Diagnostics;
|
||||||
|
|
||||||
|
internal struct ValueStopwatch
|
||||||
|
{
|
||||||
|
#if !NET7_0_OR_GREATER
|
||||||
|
private static readonly double TimestampToTicks = TimeSpan.TicksPerSecond / (double)Stopwatch.Frequency;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
private readonly long _startTimestamp;
|
||||||
|
|
||||||
|
public bool IsActive => _startTimestamp != 0;
|
||||||
|
|
||||||
|
private ValueStopwatch(long startTimestamp)
|
||||||
|
{
|
||||||
|
_startTimestamp = startTimestamp;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ValueStopwatch StartNew() => new ValueStopwatch(Stopwatch.GetTimestamp());
|
||||||
|
|
||||||
|
public static TimeSpan GetElapsedTime(long startingTimestamp, long endingTimestamp)
|
||||||
|
{
|
||||||
|
#if !NET7_0_OR_GREATER
|
||||||
|
var timestampDelta = endingTimestamp - startingTimestamp;
|
||||||
|
var ticks = (long)(TimestampToTicks * timestampDelta);
|
||||||
|
return new TimeSpan(ticks);
|
||||||
|
#else
|
||||||
|
return Stopwatch.GetElapsedTime(startingTimestamp, endingTimestamp);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
public TimeSpan GetElapsedTime()
|
||||||
|
{
|
||||||
|
// Start timestamp can't be zero in an initialized ValueStopwatch. It would have to be literally the first thing executed when the machine boots to be 0.
|
||||||
|
// So it being 0 is a clear indication of default(ValueStopwatch)
|
||||||
|
if (!IsActive)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException("An uninitialized, or 'default', ValueStopwatch cannot be used to get elapsed time.");
|
||||||
|
}
|
||||||
|
|
||||||
|
var end = Stopwatch.GetTimestamp();
|
||||||
|
|
||||||
|
return GetElapsedTime(_startTimestamp, end);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -62,7 +62,7 @@ public class ExpiringDictionary<TKey, TValue> : IDisposable
|
|||||||
this.comparer = comparer;
|
this.comparer = comparer;
|
||||||
_dict = new NonBlockingDictionary<TKey, CacheItem>(comparer);
|
_dict = new NonBlockingDictionary<TKey, CacheItem>(comparer);
|
||||||
|
|
||||||
_cleanupTimer = new TimerX(TimerClear, null, 10000, 10000) { Async = true };
|
_cleanupTimer = new TimerX(TimerClear, null, 60000, 60000) { Async = true };
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool TryAdd(TKey key, TValue value)
|
public bool TryAdd(TKey key, TValue value)
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
// QQ群:605534569
|
// QQ群:605534569
|
||||||
//------------------------------------------------------------------------------
|
//------------------------------------------------------------------------------
|
||||||
|
|
||||||
namespace ThingsGateway.Foundation;
|
namespace ThingsGateway.NewLife;
|
||||||
|
|
||||||
public class LinkedCancellationTokenSourceCache : IDisposable
|
public class LinkedCancellationTokenSourceCache : IDisposable
|
||||||
{
|
{
|
||||||
@@ -63,6 +63,7 @@ public class LinkedCancellationTokenSourceCache : IDisposable
|
|||||||
_cachedCts?.Dispose();
|
_cachedCts?.Dispose();
|
||||||
_cachedCts = null!;
|
_cachedCts = null!;
|
||||||
}
|
}
|
||||||
|
GC.SuppressFinalize(this);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -10,13 +10,18 @@
|
|||||||
// 感谢您的下载和使用
|
// 感谢您的下载和使用
|
||||||
//------------------------------------------------------------------------------
|
//------------------------------------------------------------------------------
|
||||||
|
|
||||||
namespace ThingsGateway.Foundation;
|
namespace ThingsGateway.NewLife;
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
|
|
||||||
public sealed class ReusableCancellationTokenSource : IDisposable
|
public sealed class ReusableCancellationTokenSource : IDisposable
|
||||||
{
|
{
|
||||||
|
~ReusableCancellationTokenSource()
|
||||||
|
{
|
||||||
|
Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
private readonly Timer _timer;
|
private readonly Timer _timer;
|
||||||
private CancellationTokenSource? _cts;
|
private CancellationTokenSource? _cts;
|
||||||
|
|
||||||
@@ -47,7 +52,7 @@ public sealed class ReusableCancellationTokenSource : IDisposable
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// 获取一个 CTS,并启动超时
|
/// 获取一个 CTS,并启动超时
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public CancellationTokenSource GetTokenSource(long timeout, CancellationToken external1 = default, CancellationToken external2 = default, CancellationToken external3 = default)
|
public CancellationToken GetTokenSource(long timeout, CancellationToken external1 = default, CancellationToken external2 = default, CancellationToken external3 = default)
|
||||||
{
|
{
|
||||||
TimeoutStatus = false;
|
TimeoutStatus = false;
|
||||||
|
|
||||||
@@ -57,7 +62,7 @@ public sealed class ReusableCancellationTokenSource : IDisposable
|
|||||||
// 启动 Timer
|
// 启动 Timer
|
||||||
_timer.Change(timeout, Timeout.Infinite);
|
_timer.Change(timeout, Timeout.Infinite);
|
||||||
|
|
||||||
return _cts;
|
return _cts.Token;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -71,15 +76,16 @@ public sealed class ReusableCancellationTokenSource : IDisposable
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public void Cancel()
|
public void Cancel()
|
||||||
{
|
{
|
||||||
_cts?.SafeCancel();
|
try { _cts?.Cancel(); } catch { }
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
_cts?.SafeCancel();
|
try { _cts?.Cancel(); } catch { }
|
||||||
_cts?.SafeDispose();
|
try { _cts?.Dispose(); } catch { }
|
||||||
_linkedCtsCache.SafeDispose();
|
try { _linkedCtsCache?.Dispose(); } catch { }
|
||||||
_timer.SafeDispose();
|
try { _timer?.Dispose(); } catch { }
|
||||||
|
GC.SuppressFinalize(this);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -8,6 +8,8 @@
|
|||||||
// QQ群:605534569
|
// QQ群:605534569
|
||||||
//------------------------------------------------------------------------------
|
//------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
using ThingsGateway.NewLife.Collections;
|
||||||
|
|
||||||
namespace ThingsGateway.NewLife;
|
namespace ThingsGateway.NewLife;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -90,21 +92,119 @@ public sealed class WaitLock : IDisposable
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public Task WaitAsync(CancellationToken cancellationToken = default)
|
public Task WaitAsync(CancellationToken cancellationToken = default)
|
||||||
{
|
{
|
||||||
return _waiterLock.WaitAsync(cancellationToken);
|
#if NET6_0_OR_GREATER
|
||||||
|
if (cancellationToken.CanBeCanceled)
|
||||||
|
return WaitUntilCountOrTimeoutAsync(_waiterLock.WaitAsync(Timeout.Infinite, CancellationToken.None), Timeout.Infinite, cancellationToken);
|
||||||
|
//return WaitUntilAsync2(_waiterLock.WaitAsync(Timeout.Infinite, CancellationToken.None), Timeout.Infinite, cancellationToken);
|
||||||
|
else
|
||||||
|
return _waiterLock.WaitAsync(Timeout.Infinite, CancellationToken.None);
|
||||||
|
|
||||||
|
#else
|
||||||
|
return _waiterLock.WaitAsync(Timeout.Infinite, cancellationToken);
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#if NET6_0_OR_GREATER
|
||||||
|
|
||||||
|
|
||||||
|
//private ObjectPoolLock<ReusableCancellationTokenSource> _reusableTimeouts = new();
|
||||||
|
|
||||||
|
/// <summary>Performs the asynchronous wait.</summary>
|
||||||
|
/// <param name="asyncWaiter">The asynchronous waiter.</param>
|
||||||
|
/// <param name="millisecondsTimeout">The timeout.</param>
|
||||||
|
/// <param name="cancellationToken">The cancellation token.</param>
|
||||||
|
/// <returns>The task to return to the caller.</returns>
|
||||||
|
private Task<bool> WaitUntilCountOrTimeoutAsync(Task<bool> asyncWaiter, int millisecondsTimeout, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
if (millisecondsTimeout == Timeout.Infinite)
|
||||||
|
{
|
||||||
|
return (asyncWaiter.WaitAsync(cancellationToken));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return (asyncWaiter.WaitAsync(TimeSpan.FromMilliseconds(millisecondsTimeout), cancellationToken));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//private Task WaitUntilAsync2(Task task, int timeoutMs, CancellationToken token)
|
||||||
|
//{
|
||||||
|
// if (task.IsCompleted) return task;
|
||||||
|
|
||||||
|
// var tcs = new TaskCompletionSource<object?>(TaskCreationOptions.RunContinuationsAsynchronously);
|
||||||
|
// var reusableTimeout = _reusableTimeouts.Get();
|
||||||
|
|
||||||
|
// CancellationTokenRegistration ctr = default;
|
||||||
|
|
||||||
|
// // 超时 + 取消 Token
|
||||||
|
// if (timeoutMs != Timeout.Infinite || token.CanBeCanceled)
|
||||||
|
// {
|
||||||
|
// var ctsToken = reusableTimeout.GetTokenSource(timeoutMs, token);
|
||||||
|
|
||||||
|
// ctr = ctsToken.Register(static (state, token2) =>
|
||||||
|
// {
|
||||||
|
// var (tcs2, ctoken) = ((TaskCompletionSource<object?>, CancellationToken))state!;
|
||||||
|
// if (ctoken.IsCancellationRequested)
|
||||||
|
// tcs2.TrySetCanceled(ctoken);
|
||||||
|
// else
|
||||||
|
// tcs2.TrySetException(new TimeoutException("The operation has timed out."));
|
||||||
|
// }, (tcs, token));
|
||||||
|
// }
|
||||||
|
|
||||||
|
// if (task.IsCompleted)
|
||||||
|
// {
|
||||||
|
// _reusableTimeouts.Return(reusableTimeout);
|
||||||
|
// ctr.Dispose();
|
||||||
|
// return task;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// // 监听原始任务
|
||||||
|
// task.ContinueWith(static (t, state) =>
|
||||||
|
// {
|
||||||
|
// var (tcs2, ctr2, ctsPool, cts) = ((TaskCompletionSource<object?>, CancellationTokenRegistration, ObjectPoolLock<ReusableCancellationTokenSource>, ReusableCancellationTokenSource))state!;
|
||||||
|
// try
|
||||||
|
// {
|
||||||
|
// if (t.IsCanceled)
|
||||||
|
// tcs2.TrySetCanceled();
|
||||||
|
// else if (t.IsFaulted)
|
||||||
|
// tcs2.TrySetException(t.Exception!.InnerExceptions);
|
||||||
|
// else
|
||||||
|
// tcs2.TrySetResult(null);
|
||||||
|
// }
|
||||||
|
// finally
|
||||||
|
// {
|
||||||
|
// ctsPool.Return(cts);
|
||||||
|
// ctr2.Dispose();
|
||||||
|
// }
|
||||||
|
// }, (tcs, ctr, _reusableTimeouts, reusableTimeout), CancellationToken.None, TaskContinuationOptions.ExecuteSynchronously, TaskScheduler.Default);
|
||||||
|
|
||||||
|
// return tcs.Task;
|
||||||
|
//}
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 进入锁
|
/// 进入锁
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public Task<bool> WaitAsync(int millisecondsTimeout, CancellationToken cancellationToken = default)
|
public Task<bool> WaitAsync(int millisecondsTimeout, CancellationToken cancellationToken = default)
|
||||||
{
|
{
|
||||||
|
#if NET6_0_OR_GREATER
|
||||||
|
if (cancellationToken.CanBeCanceled || millisecondsTimeout != Timeout.Infinite)
|
||||||
|
return WaitUntilCountOrTimeoutAsync(_waiterLock.WaitAsync(Timeout.Infinite, CancellationToken.None), millisecondsTimeout, cancellationToken);
|
||||||
|
else
|
||||||
|
return _waiterLock.WaitAsync(Timeout.Infinite, CancellationToken.None);
|
||||||
|
|
||||||
|
#else
|
||||||
return _waiterLock.WaitAsync(millisecondsTimeout, cancellationToken);
|
return _waiterLock.WaitAsync(millisecondsTimeout, cancellationToken);
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
bool DisposedValue;
|
bool DisposedValue;
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
DisposedValue = true;
|
DisposedValue = true;
|
||||||
|
#if NET6_0_OR_GREATER
|
||||||
|
//_reusableTimeouts?.TryDispose();
|
||||||
|
#endif
|
||||||
_waiterLock?.TryDispose();
|
_waiterLock?.TryDispose();
|
||||||
GC.SuppressFinalize(this);
|
GC.SuppressFinalize(this);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -234,7 +234,7 @@ public class TextFileLog : Logger, IDisposable
|
|||||||
if (!_isFile && Backups > 0)
|
if (!_isFile && Backups > 0)
|
||||||
{
|
{
|
||||||
// 判断日志目录是否已存在
|
// 判断日志目录是否已存在
|
||||||
var di = LogPath.GetBasePath().AsDirectory();
|
DirectoryInfo? di = new DirectoryInfo(LogPath);
|
||||||
if (di.Exists)
|
if (di.Exists)
|
||||||
{
|
{
|
||||||
// 删除*.del
|
// 删除*.del
|
||||||
|
|||||||
@@ -0,0 +1,15 @@
|
|||||||
|
using System.Runtime.CompilerServices;
|
||||||
|
|
||||||
|
namespace PooledAwait
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Provides async/await-related extension methods
|
||||||
|
/// </summary>
|
||||||
|
public static class AwaitableExtensions
|
||||||
|
{
|
||||||
|
/// <summary>Controls whether a yield operation should respect captured context</summary>
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public static ConfiguredYieldAwaitable ConfigureAwait(this YieldAwaitable _, bool continueOnCapturedContext)
|
||||||
|
=> new ConfiguredYieldAwaitable(continueOnCapturedContext);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,125 @@
|
|||||||
|
using System.Runtime.CompilerServices;
|
||||||
|
|
||||||
|
namespace PooledAwait
|
||||||
|
{
|
||||||
|
/// <summary>Provides an awaitable context for switching into a target environment.</summary>
|
||||||
|
public readonly struct ConfiguredYieldAwaitable
|
||||||
|
{
|
||||||
|
/// <summary><see cref="Object.Equals(Object)"/></summary>
|
||||||
|
public override bool Equals(object? obj) => obj is ConfiguredYieldAwaitable other && other._continueOnCapturedContext == _continueOnCapturedContext;
|
||||||
|
/// <summary><see cref="Object.GetHashCode"/></summary>
|
||||||
|
public override int GetHashCode() => _continueOnCapturedContext ? 1 : 0;
|
||||||
|
/// <summary><see cref="Object.ToString"/></summary>
|
||||||
|
public override string ToString() => nameof(ConfiguredYieldAwaitable);
|
||||||
|
|
||||||
|
private readonly bool _continueOnCapturedContext;
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
internal ConfiguredYieldAwaitable(bool continueOnCapturedContext)
|
||||||
|
=> _continueOnCapturedContext = continueOnCapturedContext;
|
||||||
|
|
||||||
|
/// <summary>Gets an awaiter for this <see cref="ConfiguredYieldAwaitable"/>.</summary>
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public ConfiguredYieldAwaiter GetAwaiter()
|
||||||
|
=> new ConfiguredYieldAwaiter(_continueOnCapturedContext);
|
||||||
|
|
||||||
|
/// <summary>Provides an awaitable context for switching into a target environment.</summary>
|
||||||
|
public readonly struct ConfiguredYieldAwaiter : ICriticalNotifyCompletion, INotifyCompletion
|
||||||
|
{
|
||||||
|
/// <summary><see cref="Object.Equals(Object)"/></summary>
|
||||||
|
public override bool Equals(object? obj) => obj is ConfiguredYieldAwaiter other && other._continueOnCapturedContext == _continueOnCapturedContext;
|
||||||
|
/// <summary><see cref="Object.GetHashCode"/></summary>
|
||||||
|
public override int GetHashCode() => _continueOnCapturedContext ? 1 : 0;
|
||||||
|
/// <summary><see cref="Object.ToString"/></summary>
|
||||||
|
public override string ToString() => nameof(ConfiguredYieldAwaiter);
|
||||||
|
|
||||||
|
private readonly bool _continueOnCapturedContext;
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
internal ConfiguredYieldAwaiter(bool continueOnCapturedContext)
|
||||||
|
=> _continueOnCapturedContext = continueOnCapturedContext;
|
||||||
|
|
||||||
|
/// <summary>Gets whether a yield is not required.</summary>
|
||||||
|
public bool IsCompleted
|
||||||
|
{
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
get => false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Ends the await operation.</summary>
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public void GetResult() { }
|
||||||
|
|
||||||
|
/// <summary>Posts the <paramref name="continuation"/> back to the current context.</summary>
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public void OnCompleted(Action continuation)
|
||||||
|
{
|
||||||
|
if (_continueOnCapturedContext) YieldFlowContext(continuation, true);
|
||||||
|
else YieldNoContext(continuation, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Posts the <paramref name="continuation"/> back to the current context.</summary>
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public void UnsafeOnCompleted(Action continuation)
|
||||||
|
{
|
||||||
|
if (_continueOnCapturedContext) YieldFlowContext(continuation, false);
|
||||||
|
else YieldNoContext(continuation, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static readonly WaitCallback s_waitCallbackRunAction = state => ((Action?)state)?.Invoke();
|
||||||
|
|
||||||
|
#if PLAT_THREADPOOLWORKITEM
|
||||||
|
private sealed class ContinuationWorkItem : IThreadPoolWorkItem
|
||||||
|
{
|
||||||
|
private Action? _continuation;
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
private ContinuationWorkItem() => Internal.Counters.ItemBoxAllocated.Increment();
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public static ContinuationWorkItem Create(Action continuation)
|
||||||
|
{
|
||||||
|
var box = Pool<ContinuationWorkItem>.TryGet() ?? new ContinuationWorkItem();
|
||||||
|
box._continuation = continuation;
|
||||||
|
return box;
|
||||||
|
}
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
void IThreadPoolWorkItem.Execute()
|
||||||
|
{
|
||||||
|
var callback = _continuation;
|
||||||
|
_continuation = null;
|
||||||
|
Pool<ContinuationWorkItem>.TryPut(this);
|
||||||
|
callback?.Invoke();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
[MethodImpl(MethodImplOptions.NoInlining)] // no-one ever calls ConfigureAwait(true)!
|
||||||
|
private static void YieldFlowContext(Action continuation, bool flowContext)
|
||||||
|
{
|
||||||
|
var awaiter = default(YieldAwaitable.YieldAwaiter);
|
||||||
|
if (flowContext) awaiter.OnCompleted(continuation);
|
||||||
|
else awaiter.UnsafeOnCompleted(continuation);
|
||||||
|
}
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
private static void YieldNoContext(Action continuation, bool flowContext)
|
||||||
|
{
|
||||||
|
if (flowContext)
|
||||||
|
{
|
||||||
|
ThreadPool.QueueUserWorkItem(s_waitCallbackRunAction, continuation);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
#if PLAT_THREADPOOLWORKITEM
|
||||||
|
ThreadPool.UnsafeQueueUserWorkItem(ContinuationWorkItem.Create(continuation), false);
|
||||||
|
#elif NETSTANDARD1_3
|
||||||
|
ThreadPool.QueueUserWorkItem(s_waitCallbackRunAction, continuation);
|
||||||
|
#else
|
||||||
|
ThreadPool.UnsafeQueueUserWorkItem(s_waitCallbackRunAction, continuation);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,62 @@
|
|||||||
|
using PooledAwait.Internal;
|
||||||
|
|
||||||
|
using System.Runtime.CompilerServices;
|
||||||
|
|
||||||
|
namespace PooledAwait
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Represents an operation that completes at the first incomplete await,
|
||||||
|
/// with the remainder continuing in the background
|
||||||
|
/// </summary>
|
||||||
|
[AsyncMethodBuilder(typeof(MethodBuilders.FireAndForgetMethodBuilder))]
|
||||||
|
public readonly struct FireAndForget
|
||||||
|
{
|
||||||
|
/// <summary><see cref="Object.Equals(Object)"/></summary>
|
||||||
|
public override bool Equals(object? obj) => ThrowHelper.ThrowNotSupportedException<bool>();
|
||||||
|
/// <summary><see cref="Object.GetHashCode"/></summary>
|
||||||
|
public override int GetHashCode() => ThrowHelper.ThrowNotSupportedException<int>();
|
||||||
|
/// <summary><see cref="Object.ToString"/></summary>
|
||||||
|
public override string ToString() => nameof(FireAndForget);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Raised when exceptions occur on fire-and-forget methods
|
||||||
|
/// </summary>
|
||||||
|
public static event Action<Exception>? Exception;
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
internal static void OnException(Exception exception)
|
||||||
|
{
|
||||||
|
if (exception != null) Exception?.Invoke(exception);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the instance as a value-task
|
||||||
|
/// </summary>
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public ValueTask AsValueTask() => default;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the instance as a task
|
||||||
|
/// </summary>
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public Task AsTask() => TaskUtils.CompletedTask;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the instance as a task
|
||||||
|
/// </summary>
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public static implicit operator Task(FireAndForget _) => TaskUtils.CompletedTask;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the instance as a value-task
|
||||||
|
/// </summary>
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public static implicit operator ValueTask(FireAndForget _) => default;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the awaiter for the instance
|
||||||
|
/// </summary>
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public ValueTaskAwaiter GetAwaiter() => default(ValueTask).GetAwaiter();
|
||||||
|
}
|
||||||
|
}
|
||||||
13
src/Admin/ThingsGateway.NewLife.X/PooledAwait/IResettable.cs
Normal file
13
src/Admin/ThingsGateway.NewLife.X/PooledAwait/IResettable.cs
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
namespace PooledAwait
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Indicates that an object can be reset
|
||||||
|
/// </summary>
|
||||||
|
public interface IResettable
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Resets this instance
|
||||||
|
/// </summary>
|
||||||
|
public void Reset();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
#if NETSTANDARD1_3
|
||||||
|
using System;
|
||||||
|
|
||||||
|
namespace PooledAwait.Internal
|
||||||
|
{
|
||||||
|
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Property | AttributeTargets.Class | AttributeTargets.Struct, AllowMultiple = false, Inherited = true)]
|
||||||
|
internal sealed class BrowsableAttribute : Attribute
|
||||||
|
{
|
||||||
|
public BrowsableAttribute(bool _) { }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
@@ -0,0 +1,68 @@
|
|||||||
|
using System.Diagnostics;
|
||||||
|
|
||||||
|
namespace PooledAwait.Internal
|
||||||
|
{
|
||||||
|
internal static class Counters
|
||||||
|
{
|
||||||
|
internal struct Counter
|
||||||
|
{
|
||||||
|
private long _value;
|
||||||
|
[Conditional("DEBUG")]
|
||||||
|
public void Increment() => Interlocked.Increment(ref _value);
|
||||||
|
#if !DEBUG
|
||||||
|
[System.Obsolete("Release only", false)]
|
||||||
|
#endif
|
||||||
|
public long Value => Interlocked.Read(ref _value);
|
||||||
|
#pragma warning disable CS0618
|
||||||
|
public override string ToString() => Value.ToString();
|
||||||
|
#pragma warning restore CS0618
|
||||||
|
public override bool Equals(object? obj) => ThrowHelper.ThrowNotSupportedException<bool>();
|
||||||
|
public override int GetHashCode() => ThrowHelper.ThrowNotSupportedException<int>();
|
||||||
|
public void Reset() => Interlocked.Exchange(ref _value, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
internal static Counter
|
||||||
|
SetStateMachine,
|
||||||
|
PooledStateAllocated,
|
||||||
|
PooledStateRecycled,
|
||||||
|
StateMachineBoxAllocated,
|
||||||
|
StateMachineBoxRecycled,
|
||||||
|
ItemBoxAllocated,
|
||||||
|
TaskAllocated,
|
||||||
|
LazyStateAllocated;
|
||||||
|
|
||||||
|
#if !DEBUG
|
||||||
|
[System.Obsolete("Release only", false)]
|
||||||
|
#endif
|
||||||
|
public static long TotalAllocations =>
|
||||||
|
PooledStateAllocated.Value + StateMachineBoxAllocated.Value
|
||||||
|
+ ItemBoxAllocated.Value + TaskAllocated.Value
|
||||||
|
+ SetStateMachine.Value // SetStateMachine usually means a boxed value
|
||||||
|
+ LazyStateAllocated.Value;
|
||||||
|
|
||||||
|
internal static void Reset()
|
||||||
|
{
|
||||||
|
SetStateMachine.Reset();
|
||||||
|
PooledStateAllocated.Reset();
|
||||||
|
PooledStateRecycled.Reset();
|
||||||
|
StateMachineBoxAllocated.Reset();
|
||||||
|
StateMachineBoxRecycled.Reset();
|
||||||
|
ItemBoxAllocated.Reset();
|
||||||
|
TaskAllocated.Reset();
|
||||||
|
LazyStateAllocated.Reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
#if !DEBUG
|
||||||
|
[System.Obsolete("Release only", false)]
|
||||||
|
#endif
|
||||||
|
internal static string Summary()
|
||||||
|
=> $@"SetStateMachine: {SetStateMachine.Value}
|
||||||
|
PooledStateAllocated: {PooledStateAllocated.Value}
|
||||||
|
PooledStateRecycled: {PooledStateRecycled.Value}
|
||||||
|
StateMachineBoxAllocated: {StateMachineBoxAllocated.Value}
|
||||||
|
StateMachineBoxRecycled: {StateMachineBoxRecycled.Value}
|
||||||
|
ItemBoxAllocated: {ItemBoxAllocated.Value}
|
||||||
|
TaskAllocated: {TaskAllocated.Value}
|
||||||
|
LazyStateAllocated: {LazyStateAllocated.Value}";
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,173 @@
|
|||||||
|
using System.Runtime.CompilerServices;
|
||||||
|
|
||||||
|
namespace PooledAwait.Internal
|
||||||
|
{
|
||||||
|
internal sealed class LazyTaskState<T>
|
||||||
|
{
|
||||||
|
private short _version;
|
||||||
|
private T _result;
|
||||||
|
private Exception? _exception;
|
||||||
|
private Task? _task;
|
||||||
|
private bool _isComplete;
|
||||||
|
private ValueTaskCompletionSource<T> _source;
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
private void CheckTokenInsideLock(short token)
|
||||||
|
{
|
||||||
|
if (token != _version) ThrowHelper.ThrowInvalidOperationException();
|
||||||
|
}
|
||||||
|
private object _syncRoot = new object();
|
||||||
|
public Task GetTask(short token)
|
||||||
|
{
|
||||||
|
lock (_syncRoot)
|
||||||
|
{
|
||||||
|
CheckTokenInsideLock(token);
|
||||||
|
if (_task != null) { }
|
||||||
|
else if (_exception is OperationCanceledException) _task = TaskUtils.TaskFactory<T>.Canceled;
|
||||||
|
else if (_exception != null) _task = TaskUtils.FromException<T>(_exception);
|
||||||
|
else if (_isComplete) _task = typeof(T) == typeof(Nothing) ? TaskUtils.CompletedTask : TaskUtils.TaskFactory<T>.FromResult(_result);
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_source = ValueTaskCompletionSource<T>.Create();
|
||||||
|
_task = _source.Task;
|
||||||
|
}
|
||||||
|
return _task;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal bool IsValid(short token) => Volatile.Read(ref _version) == token;
|
||||||
|
internal bool HasSource
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
lock (_syncRoot) { return !_source.IsNull; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
internal bool HasTask => Volatile.Read(ref _task) != null;
|
||||||
|
|
||||||
|
public bool TrySetResult(short token, T result)
|
||||||
|
{
|
||||||
|
lock (_syncRoot)
|
||||||
|
{
|
||||||
|
if (_isComplete) return false;
|
||||||
|
if (token != _version) return false;
|
||||||
|
_isComplete = true;
|
||||||
|
if (!_source.IsNull) return _source.TrySetResult(result);
|
||||||
|
_result = result;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool TrySetException(short token, Exception exception)
|
||||||
|
{
|
||||||
|
lock (_syncRoot)
|
||||||
|
{
|
||||||
|
if (_isComplete) return false;
|
||||||
|
if (token != _version) return false;
|
||||||
|
_isComplete = true;
|
||||||
|
if (!_source.IsNull) return _source.TrySetException(exception);
|
||||||
|
_exception = exception;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool TrySetCanceled(short token, CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
lock (_syncRoot)
|
||||||
|
{
|
||||||
|
if (_isComplete) return false;
|
||||||
|
if (token != _version) return false;
|
||||||
|
_isComplete = true;
|
||||||
|
if (!_source.IsNull) return _source.TrySetCanceled(cancellationToken);
|
||||||
|
_task = TaskUtils.TaskFactory<T>.Canceled;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public static LazyTaskState<T> Create() => Pool<LazyTaskState<T>>.TryGet() ?? new LazyTaskState<T>();
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
private LazyTaskState()
|
||||||
|
{
|
||||||
|
Counters.LazyStateAllocated.Increment();
|
||||||
|
_result = default!;
|
||||||
|
_version = InitialVersion;
|
||||||
|
}
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public static LazyTaskState<T> CreateConstant(T value)
|
||||||
|
{
|
||||||
|
var obj = new LazyTaskState<T>
|
||||||
|
{
|
||||||
|
_version = Constant
|
||||||
|
};
|
||||||
|
obj.TrySetResult(Constant, value);
|
||||||
|
return obj;
|
||||||
|
}
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public static LazyTaskState<T> CreateCanceled()
|
||||||
|
{
|
||||||
|
var obj = new LazyTaskState<T>
|
||||||
|
{
|
||||||
|
_version = Constant
|
||||||
|
};
|
||||||
|
obj.TrySetCanceled(Constant);
|
||||||
|
return obj;
|
||||||
|
}
|
||||||
|
|
||||||
|
const short InitialVersion = 0, Constant = InitialVersion - 1;
|
||||||
|
|
||||||
|
internal short Version
|
||||||
|
{
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
get => _version;
|
||||||
|
}
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||||
|
internal void Recycle(short token)
|
||||||
|
{
|
||||||
|
if (token == Constant) return; // never recycle constant values; this is by design
|
||||||
|
|
||||||
|
if (Volatile.Read(ref _version) != token) return; // wrong version; all bets are off!
|
||||||
|
|
||||||
|
if (!Volatile.Read(ref _isComplete)) // if incomplete, try to cancel
|
||||||
|
{
|
||||||
|
if (!TrySetCanceled(token)) return; // if that didn't work... give up - don't recycle
|
||||||
|
}
|
||||||
|
|
||||||
|
bool haveLock = false;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// only if uncontested; we're not waiting in a dispose
|
||||||
|
Monitor.TryEnter(_syncRoot, ref haveLock);
|
||||||
|
if (haveLock)
|
||||||
|
{
|
||||||
|
if (token == _version)
|
||||||
|
{
|
||||||
|
_result = default!;
|
||||||
|
_exception = default;
|
||||||
|
_task = default;
|
||||||
|
_isComplete = false;
|
||||||
|
_source = default;
|
||||||
|
|
||||||
|
switch (++_version)
|
||||||
|
{
|
||||||
|
case InitialVersion: // don't wrap all the way around when recycling; could lead to conflicts
|
||||||
|
case Constant: // don't allow things to *become* constants
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
Pool<LazyTaskState<T>>.TryPut(this);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
if (haveLock) Monitor.Exit(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,314 @@
|
|||||||
|
#if !PLAT_MRVTSC
|
||||||
|
using System;
|
||||||
|
using System.Diagnostics;
|
||||||
|
using System.Runtime.ExceptionServices;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using System.Threading.Tasks.Sources;
|
||||||
|
|
||||||
|
// from: https://raw.githubusercontent.com/dotnet/coreclr/master/src/System.Private.CoreLib/shared/System/Threading/Tasks/Sources/ManualResetValueTaskSourceCore.cs
|
||||||
|
// original license:
|
||||||
|
|
||||||
|
// Licensed to the .NET Foundation under one or more agreements.
|
||||||
|
// The .NET Foundation licenses this file to you under the MIT license.
|
||||||
|
// See the LICENSE file in the project root for more information.
|
||||||
|
|
||||||
|
namespace PooledAwait.Internal
|
||||||
|
{
|
||||||
|
internal readonly struct WaitCallbackShim
|
||||||
|
{
|
||||||
|
public readonly Action<object?>? Continuation;
|
||||||
|
public readonly object? State;
|
||||||
|
private WaitCallbackShim(Action<object?>? continuation, object? state)
|
||||||
|
{
|
||||||
|
Continuation = continuation;
|
||||||
|
State = state;
|
||||||
|
}
|
||||||
|
public static object Create(Action<object?>? continuation, object? state)
|
||||||
|
=> Pool.Box(new WaitCallbackShim(continuation, state));
|
||||||
|
|
||||||
|
private void InvokeContinuation() => Continuation?.Invoke(State);
|
||||||
|
|
||||||
|
public static readonly WaitCallback Invoke = state => Pool.UnboxAndReturn<WaitCallbackShim>(state).InvokeContinuation();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Provides the core logic for implementing a manual-reset <see cref="IValueTaskSource"/> or <see cref="IValueTaskSource{TResult}"/>.</summary>
|
||||||
|
/// <typeparam name="TResult"></typeparam>
|
||||||
|
[StructLayout(LayoutKind.Auto)]
|
||||||
|
public struct ManualResetValueTaskSourceCore<TResult>
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The callback to invoke when the operation completes if <see cref="OnCompleted"/> was called before the operation completed,
|
||||||
|
/// or <see cref="ManualResetValueTaskSourceCoreShared.s_sentinel"/> if the operation completed before a callback was supplied,
|
||||||
|
/// or null if a callback hasn't yet been provided and the operation hasn't yet completed.
|
||||||
|
/// </summary>
|
||||||
|
private Action<object?>? _continuation;
|
||||||
|
/// <summary>State to pass to <see cref="_continuation"/>.</summary>
|
||||||
|
private object? _continuationState;
|
||||||
|
/// <summary><see cref="ExecutionContext"/> to flow to the callback, or null if no flowing is required.</summary>
|
||||||
|
private ExecutionContext? _executionContext;
|
||||||
|
/// <summary>
|
||||||
|
/// A "captured" <see cref="SynchronizationContext"/> or <see cref="TaskScheduler"/> with which to invoke the callback,
|
||||||
|
/// or null if no special context is required.
|
||||||
|
/// </summary>
|
||||||
|
private object? _capturedContext;
|
||||||
|
/// <summary>Whether the current operation has completed.</summary>
|
||||||
|
private bool _completed;
|
||||||
|
/// <summary>The result with which the operation succeeded, or the default value if it hasn't yet completed or failed.</summary>
|
||||||
|
/* [AllowNull, MaybeNull] */
|
||||||
|
private TResult _result;
|
||||||
|
/// <summary>The exception with which the operation failed, or null if it hasn't yet completed or completed successfully.</summary>
|
||||||
|
private ExceptionDispatchInfo? _error;
|
||||||
|
/// <summary>The current version of this value, used to help prevent misuse.</summary>
|
||||||
|
private short _version;
|
||||||
|
|
||||||
|
/// <summary>Gets or sets whether to force continuations to run asynchronously.</summary>
|
||||||
|
/// <remarks>Continuations may run asynchronously if this is false, but they'll never run synchronously if this is true.</remarks>
|
||||||
|
public bool RunContinuationsAsynchronously { get; set; }
|
||||||
|
|
||||||
|
/// <summary>Resets to prepare for the next operation.</summary>
|
||||||
|
public void Reset()
|
||||||
|
{
|
||||||
|
// Reset/update state for the next use/await of this instance.
|
||||||
|
_version++;
|
||||||
|
_completed = false;
|
||||||
|
_result = default!; // TODO-NULLABLE: Remove ! when nullable attributes are respected
|
||||||
|
_error = null;
|
||||||
|
_executionContext = null;
|
||||||
|
_capturedContext = null;
|
||||||
|
_continuation = null;
|
||||||
|
_continuationState = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Completes with a successful result.</summary>
|
||||||
|
/// <param name="result">The result.</param>
|
||||||
|
public void SetResult(TResult result)
|
||||||
|
{
|
||||||
|
_result = result;
|
||||||
|
SignalCompletion();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Complets with an error.</summary>
|
||||||
|
/// <param name="error"></param>
|
||||||
|
public void SetException(Exception error)
|
||||||
|
{
|
||||||
|
_error = ExceptionDispatchInfo.Capture(error);
|
||||||
|
SignalCompletion();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Gets the operation version.</summary>
|
||||||
|
public short Version => _version;
|
||||||
|
|
||||||
|
/// <summary>Gets the status of the operation.</summary>
|
||||||
|
/// <param name="token">Opaque value that was provided to the <see cref="ValueTask"/>'s constructor.</param>
|
||||||
|
public ValueTaskSourceStatus GetStatus(short token)
|
||||||
|
{
|
||||||
|
ValidateToken(token);
|
||||||
|
return
|
||||||
|
_continuation == null || !_completed ? ValueTaskSourceStatus.Pending :
|
||||||
|
_error == null ? ValueTaskSourceStatus.Succeeded :
|
||||||
|
_error.SourceException is OperationCanceledException ? ValueTaskSourceStatus.Canceled :
|
||||||
|
ValueTaskSourceStatus.Faulted;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Gets the result of the operation.</summary>
|
||||||
|
/// <param name="token">Opaque value that was provided to the <see cref="ValueTask"/>'s constructor.</param>
|
||||||
|
// [StackTraceHidden]
|
||||||
|
public TResult GetResult(short token)
|
||||||
|
{
|
||||||
|
ValidateToken(token);
|
||||||
|
if (!_completed)
|
||||||
|
{
|
||||||
|
ThrowHelper.ThrowInvalidOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
_error?.Throw();
|
||||||
|
return _result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Schedules the continuation action for this operation.</summary>
|
||||||
|
/// <param name="continuation">The continuation to invoke when the operation has completed.</param>
|
||||||
|
/// <param name="state">The state object to pass to <paramref name="continuation"/> when it's invoked.</param>
|
||||||
|
/// <param name="token">Opaque value that was provided to the <see cref="ValueTask"/>'s constructor.</param>
|
||||||
|
/// <param name="flags">The flags describing the behavior of the continuation.</param>
|
||||||
|
public void OnCompleted(Action<object?> continuation, object? state, short token, ValueTaskSourceOnCompletedFlags flags)
|
||||||
|
{
|
||||||
|
if (continuation == null) ThrowHelper.ThrowArgumentNullException(nameof(continuation));
|
||||||
|
ValidateToken(token);
|
||||||
|
|
||||||
|
if ((flags & ValueTaskSourceOnCompletedFlags.FlowExecutionContext) != 0)
|
||||||
|
{
|
||||||
|
_executionContext = ExecutionContext.Capture();
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((flags & ValueTaskSourceOnCompletedFlags.UseSchedulingContext) != 0)
|
||||||
|
{
|
||||||
|
SynchronizationContext? sc = SynchronizationContext.Current;
|
||||||
|
if (sc != null && sc.GetType() != typeof(SynchronizationContext))
|
||||||
|
{
|
||||||
|
_capturedContext = sc;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
TaskScheduler ts = TaskScheduler.Current;
|
||||||
|
if (ts != TaskScheduler.Default)
|
||||||
|
{
|
||||||
|
_capturedContext = ts;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// We need to set the continuation state before we swap in the delegate, so that
|
||||||
|
// if there's a race between this and SetResult/Exception and SetResult/Exception
|
||||||
|
// sees the _continuation as non-null, it'll be able to invoke it with the state
|
||||||
|
// stored here. However, this also means that if this is used incorrectly (e.g.
|
||||||
|
// awaited twice concurrently), _continuationState might get erroneously overwritten.
|
||||||
|
// To minimize the chances of that, we check preemptively whether _continuation
|
||||||
|
// is already set to something other than the completion sentinel.
|
||||||
|
|
||||||
|
object? oldContinuation = _continuation;
|
||||||
|
if (oldContinuation == null)
|
||||||
|
{
|
||||||
|
_continuationState = state;
|
||||||
|
oldContinuation = Interlocked.CompareExchange(ref _continuation, continuation, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (oldContinuation != null)
|
||||||
|
{
|
||||||
|
// Operation already completed, so we need to queue the supplied callback.
|
||||||
|
if (!ReferenceEquals(oldContinuation, ManualResetValueTaskSourceCoreShared.s_sentinel))
|
||||||
|
{
|
||||||
|
ThrowHelper.ThrowInvalidOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (_capturedContext)
|
||||||
|
{
|
||||||
|
case null:
|
||||||
|
if (_executionContext != null)
|
||||||
|
{
|
||||||
|
ThreadPool.QueueUserWorkItem(WaitCallbackShim.Invoke, WaitCallbackShim.Create(continuation, state));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
#if NETSTANDARD1_3
|
||||||
|
ThreadPool.QueueUserWorkItem(
|
||||||
|
#else
|
||||||
|
ThreadPool.UnsafeQueueUserWorkItem(
|
||||||
|
#endif
|
||||||
|
WaitCallbackShim.Invoke, WaitCallbackShim.Create(continuation, state));
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case SynchronizationContext sc:
|
||||||
|
sc.Post(s =>
|
||||||
|
{
|
||||||
|
var tuple = (Tuple<Action<object?>, object?>)s!;
|
||||||
|
tuple.Item1(tuple.Item2);
|
||||||
|
}, Tuple.Create(continuation, state));
|
||||||
|
break;
|
||||||
|
|
||||||
|
case TaskScheduler ts:
|
||||||
|
Task.Factory.StartNew(continuation, state, CancellationToken.None, TaskCreationOptions.DenyChildAttach, ts);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Ensures that the specified token matches the current version.</summary>
|
||||||
|
/// <param name="token">The token supplied by <see cref="ValueTask"/>.</param>
|
||||||
|
private void ValidateToken(short token)
|
||||||
|
{
|
||||||
|
if (token != _version)
|
||||||
|
{
|
||||||
|
ThrowHelper.ThrowInvalidOperationException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Signals that the operation has completed. Invoked after the result or error has been set.</summary>
|
||||||
|
private void SignalCompletion()
|
||||||
|
{
|
||||||
|
if (_completed)
|
||||||
|
{
|
||||||
|
ThrowHelper.ThrowInvalidOperationException();
|
||||||
|
}
|
||||||
|
_completed = true;
|
||||||
|
|
||||||
|
if (_continuation != null || Interlocked.CompareExchange(ref _continuation, ManualResetValueTaskSourceCoreShared.s_sentinel, null) != null)
|
||||||
|
{
|
||||||
|
if (_executionContext != null)
|
||||||
|
{
|
||||||
|
ExecutionContext.Run(
|
||||||
|
_executionContext,
|
||||||
|
s_UnboxAndInvokeContextCallback,
|
||||||
|
Pool.Box<ManualResetValueTaskSourceCore<TResult>>(this));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
InvokeContinuation();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static readonly ContextCallback s_UnboxAndInvokeContextCallback = state => Pool.UnboxAndReturn<ManualResetValueTaskSourceCore<TResult>>(state).InvokeContinuation();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Invokes the continuation with the appropriate captured context / scheduler.
|
||||||
|
/// This assumes that if <see cref="_executionContext"/> is not null we're already
|
||||||
|
/// running within that <see cref="ExecutionContext"/>.
|
||||||
|
/// </summary>
|
||||||
|
private void InvokeContinuation()
|
||||||
|
{
|
||||||
|
Debug.Assert(_continuation != null);
|
||||||
|
|
||||||
|
switch (_capturedContext)
|
||||||
|
{
|
||||||
|
case null:
|
||||||
|
if (RunContinuationsAsynchronously)
|
||||||
|
{
|
||||||
|
if (_executionContext != null)
|
||||||
|
{
|
||||||
|
ThreadPool.QueueUserWorkItem(WaitCallbackShim.Invoke, WaitCallbackShim.Create(_continuation, _continuationState));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
#if NETSTANDARD1_3
|
||||||
|
ThreadPool.QueueUserWorkItem(
|
||||||
|
#else
|
||||||
|
ThreadPool.UnsafeQueueUserWorkItem(
|
||||||
|
#endif
|
||||||
|
WaitCallbackShim.Invoke, WaitCallbackShim.Create(_continuation, _continuationState));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_continuation!(_continuationState);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case SynchronizationContext sc:
|
||||||
|
sc.Post(s =>
|
||||||
|
{
|
||||||
|
var state = (Tuple<Action<object?>, object?>)s!;
|
||||||
|
state.Item1(state.Item2);
|
||||||
|
}, Tuple.Create(_continuation, _continuationState));
|
||||||
|
break;
|
||||||
|
|
||||||
|
case TaskScheduler ts:
|
||||||
|
Task.Factory.StartNew(_continuation, _continuationState, CancellationToken.None, TaskCreationOptions.DenyChildAttach, ts);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal static class ManualResetValueTaskSourceCoreShared // separated out of generic to avoid unnecessary duplication
|
||||||
|
{
|
||||||
|
internal static readonly Action<object?> s_sentinel = CompletionSentinel;
|
||||||
|
private static void CompletionSentinel(object? _) // named method to aid debugging
|
||||||
|
{
|
||||||
|
Debug.Fail("The sentinel delegate should never be invoked.");
|
||||||
|
ThrowHelper.ThrowInvalidOperationException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
namespace PooledAwait.Internal
|
||||||
|
{
|
||||||
|
internal readonly struct Nothing // to express ValueTask via PooledState<Nothing>
|
||||||
|
{
|
||||||
|
public override string ToString() => nameof(Nothing);
|
||||||
|
public override int GetHashCode() => 0;
|
||||||
|
public override bool Equals(object? obj) => obj is Nothing;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,124 @@
|
|||||||
|
using System.Runtime.CompilerServices;
|
||||||
|
using System.Threading.Tasks.Sources;
|
||||||
|
|
||||||
|
namespace PooledAwait.Internal
|
||||||
|
{
|
||||||
|
internal sealed class PooledState<T> : IValueTaskSource<T>, IValueTaskSource
|
||||||
|
{
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public static PooledState<T> Create(out short token)
|
||||||
|
{
|
||||||
|
var obj = Pool<PooledState<T>>.TryGet() ?? new PooledState<T>();
|
||||||
|
token = obj._source.Version;
|
||||||
|
return obj;
|
||||||
|
}
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
private PooledState() => Counters.PooledStateAllocated.Increment();
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
internal bool IsValid(short token) => _source.Version == token;
|
||||||
|
|
||||||
|
private ManualResetValueTaskSourceCore<T> _source; // needs to be mutable
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public T GetResult(short token)
|
||||||
|
{
|
||||||
|
// we only support getting the result once; doing this recycles the source and advances the token
|
||||||
|
|
||||||
|
lock (SyncLock) // we need to be really paranoid about cross-threading over changing the token
|
||||||
|
{
|
||||||
|
var status = _source.GetStatus(token); // do this *outside* the try/finally
|
||||||
|
try // so that we don't increment the counter if someone asks for the wrong value
|
||||||
|
{
|
||||||
|
switch (status)
|
||||||
|
{
|
||||||
|
case ValueTaskSourceStatus.Canceled:
|
||||||
|
ThrowHelper.ThrowTaskCanceledException();
|
||||||
|
break;
|
||||||
|
case ValueTaskSourceStatus.Pending:
|
||||||
|
Monitor.Wait(SyncLock);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return _source.GetResult(token);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
_source.Reset();
|
||||||
|
if (_source.Version != TaskUtils.InitialTaskSourceVersion)
|
||||||
|
{
|
||||||
|
Pool<PooledState<T>>.TryPut(this);
|
||||||
|
Counters.PooledStateRecycled.Increment();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
void IValueTaskSource.GetResult(short token) => GetResult(token);
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||||
|
private void SignalResult(short token)
|
||||||
|
{
|
||||||
|
lock (SyncLock)
|
||||||
|
{
|
||||||
|
if (token == _source.Version && _source.GetStatus(token) != ValueTaskSourceStatus.Pending)
|
||||||
|
{
|
||||||
|
Monitor.Pulse(SyncLock);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public ValueTaskSourceStatus GetStatus(short token) => _source.GetStatus(token);
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public void OnCompleted(Action<object?> continuation, object? state, short token, ValueTaskSourceOnCompletedFlags flags)
|
||||||
|
=> _source.OnCompleted(continuation, state, token, flags);
|
||||||
|
|
||||||
|
private object SyncLock
|
||||||
|
{
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
get => this;
|
||||||
|
}
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public bool TrySetException(Exception error, short token)
|
||||||
|
{
|
||||||
|
if (token == _source.Version)
|
||||||
|
{
|
||||||
|
|
||||||
|
switch (_source.GetStatus(token))
|
||||||
|
{
|
||||||
|
case ValueTaskSourceStatus.Pending:
|
||||||
|
_source.SetException(error);
|
||||||
|
// only need to signal if SetException didn't inline a handler
|
||||||
|
if (token == _source.Version) SignalResult(token);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public bool TrySetResult(T result, short token)
|
||||||
|
{
|
||||||
|
if (token == _source.Version)
|
||||||
|
{
|
||||||
|
switch (_source.GetStatus(token))
|
||||||
|
{
|
||||||
|
case ValueTaskSourceStatus.Pending:
|
||||||
|
_source.SetResult(result);
|
||||||
|
// only need to signal if SetResult didn't inline a handler
|
||||||
|
if (token == _source.Version) SignalResult(token);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public bool TrySetCanceled(short token)
|
||||||
|
=> TrySetException(TaskUtils.SharedTaskCanceledException, token);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,113 @@
|
|||||||
|
using System.Runtime.CompilerServices;
|
||||||
|
|
||||||
|
namespace PooledAwait.Internal
|
||||||
|
{
|
||||||
|
internal sealed class StateMachineBox<TStateMachine>
|
||||||
|
#if PLAT_THREADPOOLWORKITEM
|
||||||
|
: IThreadPoolWorkItem
|
||||||
|
#endif
|
||||||
|
where TStateMachine : IAsyncStateMachine
|
||||||
|
{
|
||||||
|
private readonly Action _execute;
|
||||||
|
private TStateMachine _stateMachine;
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
private StateMachineBox()
|
||||||
|
{
|
||||||
|
_stateMachine = default!;
|
||||||
|
_execute = Execute;
|
||||||
|
Counters.StateMachineBoxAllocated.Increment();
|
||||||
|
}
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
private static StateMachineBox<TStateMachine> Create(ref TStateMachine stateMachine)
|
||||||
|
{
|
||||||
|
var box = Pool<StateMachineBox<TStateMachine>>.TryGet() ?? new StateMachineBox<TStateMachine>();
|
||||||
|
box._stateMachine = stateMachine;
|
||||||
|
return box;
|
||||||
|
}
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public static void AwaitOnCompleted<TAwaiter>(
|
||||||
|
ref TAwaiter awaiter, ref TStateMachine stateMachine)
|
||||||
|
where TAwaiter : INotifyCompletion
|
||||||
|
{
|
||||||
|
var box = Create(ref stateMachine);
|
||||||
|
if (typeof(TAwaiter) == typeof(YieldAwaitable.YieldAwaiter))
|
||||||
|
{
|
||||||
|
Yield(box, true);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
awaiter.OnCompleted(box._execute);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public static void AwaitUnsafeOnCompleted<TAwaiter>(
|
||||||
|
ref TAwaiter awaiter, ref TStateMachine stateMachine)
|
||||||
|
where TAwaiter : ICriticalNotifyCompletion
|
||||||
|
{
|
||||||
|
var box = Create(ref stateMachine);
|
||||||
|
if (typeof(TAwaiter) == typeof(YieldAwaitable.YieldAwaiter))
|
||||||
|
{
|
||||||
|
Yield(box, false);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
awaiter.UnsafeOnCompleted(box._execute);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void Yield(StateMachineBox<TStateMachine> box, bool flowContext)
|
||||||
|
{
|
||||||
|
// heavily inspired by YieldAwaitable.QueueContinuation
|
||||||
|
|
||||||
|
var syncContext = SynchronizationContext.Current;
|
||||||
|
if (syncContext != null && syncContext.GetType() != typeof(SynchronizationContext))
|
||||||
|
{
|
||||||
|
syncContext.Post(s_SendOrPostCallback, box);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var taskScheduler = TaskScheduler.Current;
|
||||||
|
if (!ReferenceEquals(taskScheduler, TaskScheduler.Default))
|
||||||
|
{
|
||||||
|
Task.Factory.StartNew(box._execute, default, TaskCreationOptions.PreferFairness, taskScheduler);
|
||||||
|
}
|
||||||
|
else if (flowContext)
|
||||||
|
{
|
||||||
|
ThreadPool.QueueUserWorkItem(s_WaitCallback, box);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
#if PLAT_THREADPOOLWORKITEM
|
||||||
|
ThreadPool.UnsafeQueueUserWorkItem(box, false);
|
||||||
|
#elif NETSTANDARD1_3
|
||||||
|
ThreadPool.QueueUserWorkItem(s_WaitCallback, box);
|
||||||
|
#else
|
||||||
|
ThreadPool.UnsafeQueueUserWorkItem(s_WaitCallback, box);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static readonly SendOrPostCallback s_SendOrPostCallback = state => ((StateMachineBox<TStateMachine>?)state!)?.Execute();
|
||||||
|
static readonly WaitCallback s_WaitCallback = state => ((StateMachineBox<TStateMachine>?)state)?.Execute();
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public void Execute()
|
||||||
|
{
|
||||||
|
// extract the state
|
||||||
|
var tmp = _stateMachine;
|
||||||
|
|
||||||
|
// recycle the instance
|
||||||
|
_stateMachine = default!;
|
||||||
|
Pool<StateMachineBox<TStateMachine>>.TryPut(this);
|
||||||
|
Counters.StateMachineBoxRecycled.Increment();
|
||||||
|
|
||||||
|
// progress the state machine
|
||||||
|
tmp.MoveNext();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,171 @@
|
|||||||
|
using System.Runtime.CompilerServices;
|
||||||
|
using System.Threading.Tasks.Sources;
|
||||||
|
|
||||||
|
namespace PooledAwait.Internal
|
||||||
|
{
|
||||||
|
// NET45 lacks some useful Task APIs; shim over them
|
||||||
|
internal static class TaskUtils
|
||||||
|
{
|
||||||
|
internal static readonly short InitialTaskSourceVersion = new ManualResetValueTaskSourceCore<Nothing>().Version;
|
||||||
|
|
||||||
|
public static readonly TaskCanceledException SharedTaskCanceledException = new TaskCanceledException();
|
||||||
|
#if NET45
|
||||||
|
public static readonly Task CompletedTask = TaskFactory<Nothing>.FromResult(default);
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public static Task<T> FromException<T>(Exception exception)
|
||||||
|
{
|
||||||
|
var source = ValueTaskCompletionSource<T>.Create();
|
||||||
|
source.TrySetException(exception);
|
||||||
|
return source.Task;
|
||||||
|
}
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public static Task FromException(Exception exception) => FromException<bool>(exception);
|
||||||
|
#else
|
||||||
|
public static readonly Task CompletedTask = Task.CompletedTask;
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public static Task<T> FromException<T>(Exception exception) => Task.FromException<T>(exception);
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public static Task FromException(Exception exception) => Task.FromException(exception);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
|
||||||
|
internal static class TaskFactory<TResult>
|
||||||
|
{
|
||||||
|
// draws from AsyncMethodBuilder, but less boxing
|
||||||
|
|
||||||
|
public static readonly Task<TResult> Canceled = CreateCanceled();
|
||||||
|
|
||||||
|
static Task<TResult> CreateCanceled()
|
||||||
|
{
|
||||||
|
var source = ValueTaskCompletionSource<TResult>.Create();
|
||||||
|
source.TrySetCanceled();
|
||||||
|
return source.Task;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static readonly TaskCache<TResult> _cache = (TaskCache<TResult>)CreateCacheForType();
|
||||||
|
|
||||||
|
private static object CreateCacheForType()
|
||||||
|
{
|
||||||
|
if (typeof(TResult) == typeof(Nothing)) return new NothingTaskCache();
|
||||||
|
if (typeof(TResult) == typeof(int)) return new Int32TaskCache();
|
||||||
|
if (typeof(TResult) == typeof(int?)) return new NullableInt32TaskCache();
|
||||||
|
if (typeof(TResult) == typeof(bool)) return new BooleanTaskCache();
|
||||||
|
if (typeof(TResult) == typeof(bool?)) return new NullableBooleanTaskCache();
|
||||||
|
|
||||||
|
Type underlyingType = Nullable.GetUnderlyingType(typeof(TResult)) ?? typeof(TResult);
|
||||||
|
if (underlyingType == typeof(uint)
|
||||||
|
|| underlyingType == typeof(byte)
|
||||||
|
|| underlyingType == typeof(sbyte)
|
||||||
|
|| underlyingType == typeof(char)
|
||||||
|
|| underlyingType == typeof(decimal)
|
||||||
|
|| underlyingType == typeof(long)
|
||||||
|
|| underlyingType == typeof(ulong)
|
||||||
|
|| underlyingType == typeof(short)
|
||||||
|
|| underlyingType == typeof(ushort)
|
||||||
|
|| underlyingType == typeof(float)
|
||||||
|
|| underlyingType == typeof(double)
|
||||||
|
|| underlyingType == typeof(IntPtr)
|
||||||
|
|| underlyingType == typeof(UIntPtr)
|
||||||
|
) return new DefaultEquatableTaskCache<TResult>();
|
||||||
|
|
||||||
|
if (typeof(TResult) == typeof(string)) return new StringTaskCache();
|
||||||
|
#if !NETSTANDARD1_3
|
||||||
|
if (!typeof(TResult).IsValueType) return new ObjectTaskCache<TResult>();
|
||||||
|
#endif
|
||||||
|
return new TaskCache<TResult>();
|
||||||
|
}
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public static Task<TResult> FromResult(TResult result) => _cache.FromResult(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
class TaskCache<TResult>
|
||||||
|
{
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
internal virtual Task<TResult> FromResult(TResult value) => Task.FromResult(value);
|
||||||
|
}
|
||||||
|
class NothingTaskCache : TaskCache<Nothing>
|
||||||
|
{
|
||||||
|
private static readonly Task<Nothing> s_Instance = Task.FromResult<Nothing>(default);
|
||||||
|
internal override Task<Nothing> FromResult(Nothing value) => s_Instance;
|
||||||
|
}
|
||||||
|
class DefaultEquatableTaskCache<TResult> : TaskCache<TResult>
|
||||||
|
{
|
||||||
|
private static readonly Task<TResult> s_Default = Task.FromResult<TResult>(default!);
|
||||||
|
private static readonly EqualityComparer<TResult> _comparer = EqualityComparer<TResult>.Default;
|
||||||
|
internal override Task<TResult> FromResult(TResult value)
|
||||||
|
=> _comparer.Equals(value, default!) ? s_Default : base.FromResult(value);
|
||||||
|
}
|
||||||
|
class ObjectTaskCache<TResult> : TaskCache<TResult>
|
||||||
|
{
|
||||||
|
private static readonly Task<TResult> s_Null = Task.FromResult<TResult>(default!);
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
internal override Task<TResult> FromResult(TResult value)
|
||||||
|
=> value == null ? s_Null : base.FromResult(value);
|
||||||
|
}
|
||||||
|
sealed class StringTaskCache : ObjectTaskCache<string>
|
||||||
|
{
|
||||||
|
private static readonly Task<string> s_Empty = Task.FromResult<string>("");
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
internal override Task<string> FromResult(string value)
|
||||||
|
=> string.IsNullOrEmpty(value) ? s_Empty : base.FromResult(value);
|
||||||
|
}
|
||||||
|
sealed class BooleanTaskCache : TaskCache<bool>
|
||||||
|
{
|
||||||
|
static readonly Task<bool> s_True = Task.FromResult(true), s_False = Task.FromResult(false);
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
internal override Task<bool> FromResult(bool value) => value ? s_True : s_False;
|
||||||
|
}
|
||||||
|
sealed class NullableBooleanTaskCache : TaskCache<bool?>
|
||||||
|
{
|
||||||
|
static readonly Task<bool?> s_True = Task.FromResult((bool?)true), s_False = Task.FromResult((bool?)false),
|
||||||
|
s_Null = Task.FromResult((bool?)null);
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
internal override Task<bool?> FromResult(bool? value) =>
|
||||||
|
value.HasValue ? (value.GetValueOrDefault() ? s_True : s_False) : s_Null;
|
||||||
|
}
|
||||||
|
sealed class Int32TaskCache : TaskCache<int>
|
||||||
|
{
|
||||||
|
const int MIN_INC = -1, MAX_EXC = 11;
|
||||||
|
static readonly Task<int>[] s_Known = CreateKnown();
|
||||||
|
static Task<int>[] CreateKnown()
|
||||||
|
{
|
||||||
|
var arr = new Task<int>[MAX_EXC - MIN_INC];
|
||||||
|
for (int i = 0; i < arr.Length; i++)
|
||||||
|
arr[i] = Task.FromResult(i + MIN_INC);
|
||||||
|
return arr;
|
||||||
|
}
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
internal override Task<int> FromResult(int value)
|
||||||
|
=> value >= MIN_INC && value < MAX_EXC ? s_Known[value - MIN_INC] : base.FromResult(value);
|
||||||
|
}
|
||||||
|
sealed class NullableInt32TaskCache : TaskCache<int?>
|
||||||
|
{
|
||||||
|
const int MIN_INC = -1, MAX_EXC = 11;
|
||||||
|
static readonly Task<int?>[] s_Known = CreateKnown();
|
||||||
|
static readonly Task<int?> s_Null = Task.FromResult((int?)null);
|
||||||
|
static Task<int?>[] CreateKnown()
|
||||||
|
{
|
||||||
|
var arr = new Task<int?>[MAX_EXC - MIN_INC];
|
||||||
|
for (int i = 0; i < arr.Length; i++)
|
||||||
|
arr[i] = Task.FromResult((int?)(i + MIN_INC));
|
||||||
|
return arr;
|
||||||
|
}
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
internal override Task<int?> FromResult(int? nullable)
|
||||||
|
{
|
||||||
|
if (nullable.HasValue)
|
||||||
|
{
|
||||||
|
int value = nullable.GetValueOrDefault();
|
||||||
|
return value >= MIN_INC && value < MAX_EXC ? s_Known[value - MIN_INC] : base.FromResult(nullable);
|
||||||
|
}
|
||||||
|
return s_Null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
using System.Runtime.CompilerServices;
|
||||||
|
|
||||||
|
namespace PooledAwait.Internal
|
||||||
|
{
|
||||||
|
internal static class ThrowHelper
|
||||||
|
{
|
||||||
|
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||||
|
internal static void ThrowInvalidOperationException(string? message = null)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(message)) throw new InvalidOperationException();
|
||||||
|
else throw new InvalidOperationException(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||||
|
internal static T ThrowInvalidOperationException<T>(string? message = null)
|
||||||
|
{
|
||||||
|
ThrowInvalidOperationException(message);
|
||||||
|
return default!;
|
||||||
|
}
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||||
|
internal static T ThrowNotSupportedException<T>() => throw new NotSupportedException();
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||||
|
internal static void ThrowArgumentNullException(string paramName) => throw new ArgumentNullException(paramName);
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||||
|
internal static void ThrowTaskCanceledException() => throw new TaskCanceledException();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,127 @@
|
|||||||
|
using PooledAwait.Internal;
|
||||||
|
|
||||||
|
using System.Runtime.CompilerServices;
|
||||||
|
|
||||||
|
namespace PooledAwait
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Like a ValueTaskCompletionSource, but the actual task will only become allocated
|
||||||
|
/// if the .Task is consumed; this is useful for APIs where the consumer *may* consume a task
|
||||||
|
/// </summary>
|
||||||
|
public readonly struct LazyTaskCompletionSource : IDisposable
|
||||||
|
{
|
||||||
|
|
||||||
|
private static LazyTaskCompletionSource _completed, _canceled;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A global LazyTaskCompletionSource that represents a completed operation
|
||||||
|
/// </summary>
|
||||||
|
public static LazyTaskCompletionSource CompletedTask
|
||||||
|
{
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
get => _completed.IsValid ? _completed : _completed = new LazyTaskCompletionSource(LazyTaskState<Nothing>.CreateConstant(default));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A global LazyTaskCompletionSource that represents a cancelled operation
|
||||||
|
/// </summary>
|
||||||
|
public static LazyTaskCompletionSource CanceledTask
|
||||||
|
{
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
get => _canceled.IsValid ? _canceled : _canceled = new LazyTaskCompletionSource(LazyTaskState<Nothing>.CreateCanceled());
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary><see cref="Object.Equals(Object)"/></summary>
|
||||||
|
public override bool Equals(object? obj) => obj is LazyTaskCompletionSource ltcs && _state == ltcs._state && _token == ltcs._token;
|
||||||
|
/// <summary><see cref="Object.GetHashCode"/></summary>
|
||||||
|
public override int GetHashCode() => (_state == null ? 0 : _state.GetHashCode()) ^ _token;
|
||||||
|
/// <summary><see cref="Object.ToString"/></summary>
|
||||||
|
public override string ToString() => nameof(LazyTaskCompletionSource);
|
||||||
|
|
||||||
|
private readonly LazyTaskState<Nothing> _state;
|
||||||
|
private readonly short _token;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the task associated with this instance
|
||||||
|
/// </summary>
|
||||||
|
public Task Task => _state?.GetTask(_token) ?? ThrowHelper.ThrowInvalidOperationException<Task>();
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
private LazyTaskCompletionSource(LazyTaskState<Nothing> state)
|
||||||
|
{
|
||||||
|
_state = state;
|
||||||
|
_token = state.Version;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Create a new instance; this instance should be disposed when it is known to be unwanted
|
||||||
|
/// </summary>
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public static LazyTaskCompletionSource Create()
|
||||||
|
=> new LazyTaskCompletionSource(LazyTaskState<Nothing>.Create());
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Set the result of the operation
|
||||||
|
/// </summary>
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public bool TrySetResult() => _state?.TrySetResult(_token, default) == true;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Set the result of the operation
|
||||||
|
/// </summary>
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public bool TrySetCanceled(CancellationToken cancellationToken = default) => _state?.TrySetCanceled(_token, cancellationToken) == true;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Set the result of the operation
|
||||||
|
/// </summary>
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public void SetResult()
|
||||||
|
{
|
||||||
|
if (!TrySetResult()) ThrowHelper.ThrowInvalidOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Set the result of the operation
|
||||||
|
/// </summary>
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public void SetCanceled(CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
if (!TrySetCanceled(cancellationToken)) ThrowHelper.ThrowInvalidOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Set the result of the operation
|
||||||
|
/// </summary>
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public bool TrySetException(Exception exception) => _state?.TrySetException(_token, exception) == true;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Set the result of the operation
|
||||||
|
/// </summary>
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public void SetException(Exception exception)
|
||||||
|
{
|
||||||
|
if (!TrySetException(exception)) ThrowHelper.ThrowInvalidOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Release all resources associated with this operation
|
||||||
|
/// </summary>
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public void Dispose() => _state?.Recycle(_token);
|
||||||
|
|
||||||
|
internal bool IsValid => _state?.IsValid(_token) == true;
|
||||||
|
internal bool HasSource => _state?.HasSource == true;
|
||||||
|
internal bool HasTask => _state?.HasTask == true;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Indicates whether this is an invalid default instance
|
||||||
|
/// </summary>
|
||||||
|
public bool IsNull
|
||||||
|
{
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
get => _state == null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,126 @@
|
|||||||
|
using PooledAwait.Internal;
|
||||||
|
|
||||||
|
using System.Runtime.CompilerServices;
|
||||||
|
|
||||||
|
namespace PooledAwait
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Like a ValueTaskCompletionSource<typeparamref name="T"/>, but the actual task will only become allocated
|
||||||
|
/// if the .Task is consumed; this is useful for APIs where the consumer *may* consume a task
|
||||||
|
/// </summary>
|
||||||
|
public readonly struct LazyTaskCompletionSource<T> : IDisposable
|
||||||
|
{
|
||||||
|
/// <summary><see cref="Object.Equals(Object)"/></summary>
|
||||||
|
public override bool Equals(object? obj) => obj is LazyTaskCompletionSource<T> ltcs && _state == ltcs._state && _token == ltcs._token;
|
||||||
|
/// <summary><see cref="Object.GetHashCode"/></summary>
|
||||||
|
public override int GetHashCode() => (_state == null ? 0 : _state.GetHashCode()) ^ _token;
|
||||||
|
/// <summary><see cref="Object.ToString"/></summary>
|
||||||
|
public override string ToString() => nameof(LazyTaskCompletionSource);
|
||||||
|
|
||||||
|
private readonly LazyTaskState<T> _state;
|
||||||
|
private readonly short _token;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the task associated with this instance
|
||||||
|
/// </summary>
|
||||||
|
public Task<T> Task => (Task<T>)(_state?.GetTask(_token) ?? ThrowHelper.ThrowInvalidOperationException<Task<T>>());
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
private LazyTaskCompletionSource(LazyTaskState<T> state)
|
||||||
|
{
|
||||||
|
_state = state;
|
||||||
|
_token = state.Version;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Create a new instance; this instance should be disposed when it is known to be unwanted
|
||||||
|
/// </summary>
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public static LazyTaskCompletionSource<T> Create()
|
||||||
|
=> new LazyTaskCompletionSource<T>(LazyTaskState<T>.Create());
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Create a new instance; this instance will never by recycled
|
||||||
|
/// </summary>
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public static LazyTaskCompletionSource<T> CreateConstant(T value)
|
||||||
|
=> new LazyTaskCompletionSource<T>(LazyTaskState<T>.CreateConstant(value));
|
||||||
|
|
||||||
|
|
||||||
|
private static LazyTaskCompletionSource<T> _canceled;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A global LazyTaskCompletionSource that represents a cancelled operation
|
||||||
|
/// </summary>
|
||||||
|
public static LazyTaskCompletionSource<T> CanceledTask
|
||||||
|
{
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
get => _canceled.IsValid ? _canceled : _canceled = new LazyTaskCompletionSource<T>(LazyTaskState<T>.CreateCanceled());
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Set the result of the operation
|
||||||
|
/// </summary>
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public bool TrySetResult(T result) => _state?.TrySetResult(_token, result) == true;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Set the result of the operation
|
||||||
|
/// </summary>
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public void SetResult(T result)
|
||||||
|
{
|
||||||
|
if (!TrySetResult(result)) ThrowHelper.ThrowInvalidOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Set the result of the operation
|
||||||
|
/// </summary>
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public bool TrySetCanceled(CancellationToken cancellationToken = default)
|
||||||
|
=> _state?.TrySetCanceled(_token, cancellationToken) == true;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Set the result of the operation
|
||||||
|
/// </summary>
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public void SetCanceled(CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
if (!TrySetCanceled(cancellationToken)) ThrowHelper.ThrowInvalidOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Set the result of the operation
|
||||||
|
/// </summary>
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public bool TrySetException(Exception exception) => _state?.TrySetException(_token, exception) == true;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Set the result of the operation
|
||||||
|
/// </summary>
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public void SetException(Exception exception)
|
||||||
|
{
|
||||||
|
if (!TrySetException(exception)) ThrowHelper.ThrowInvalidOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Release all resources associated with this operation
|
||||||
|
/// </summary>
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public void Dispose() => _state?.Recycle(_token);
|
||||||
|
|
||||||
|
internal bool IsValid => _state?.IsValid(_token) == true;
|
||||||
|
internal bool HasSource => _state?.HasSource == true;
|
||||||
|
internal bool HasTask => _state?.HasTask == true;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Indicates whether this is an invalid default instance
|
||||||
|
/// </summary>
|
||||||
|
public bool IsNull
|
||||||
|
{
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
get => _state == null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,73 @@
|
|||||||
|
using PooledAwait.Internal;
|
||||||
|
|
||||||
|
using System.ComponentModel;
|
||||||
|
using System.Runtime.CompilerServices;
|
||||||
|
|
||||||
|
#pragma warning disable CS1591
|
||||||
|
|
||||||
|
namespace PooledAwait.MethodBuilders
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// This type is not intended for direct usage
|
||||||
|
/// </summary>
|
||||||
|
[Browsable(false)]
|
||||||
|
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||||
|
public struct FireAndForgetMethodBuilder
|
||||||
|
{
|
||||||
|
public override bool Equals(object? obj) => ThrowHelper.ThrowNotSupportedException<bool>();
|
||||||
|
public override int GetHashCode() => ThrowHelper.ThrowNotSupportedException<int>();
|
||||||
|
public override string ToString() => nameof(FireAndForgetMethodBuilder);
|
||||||
|
|
||||||
|
[Browsable(false)]
|
||||||
|
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public static FireAndForgetMethodBuilder Create() => default;
|
||||||
|
|
||||||
|
[Browsable(false)]
|
||||||
|
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public void SetStateMachine(IAsyncStateMachine _) => Counters.SetStateMachine.Increment();
|
||||||
|
|
||||||
|
[Browsable(false)]
|
||||||
|
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public void SetResult() { }
|
||||||
|
|
||||||
|
[Browsable(false)]
|
||||||
|
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public void SetException(Exception exception) => FireAndForget.OnException(exception);
|
||||||
|
|
||||||
|
[Browsable(false)]
|
||||||
|
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||||
|
public FireAndForget Task
|
||||||
|
{
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
get => default;
|
||||||
|
}
|
||||||
|
|
||||||
|
[Browsable(false)]
|
||||||
|
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public void AwaitOnCompleted<TAwaiter, TStateMachine>(
|
||||||
|
ref TAwaiter awaiter, ref TStateMachine stateMachine)
|
||||||
|
where TAwaiter : INotifyCompletion
|
||||||
|
where TStateMachine : IAsyncStateMachine
|
||||||
|
=> StateMachineBox<TStateMachine>.AwaitOnCompleted(ref awaiter, ref stateMachine);
|
||||||
|
|
||||||
|
[Browsable(false)]
|
||||||
|
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public void AwaitUnsafeOnCompleted<TAwaiter, TStateMachine>(
|
||||||
|
ref TAwaiter awaiter, ref TStateMachine stateMachine)
|
||||||
|
where TAwaiter : ICriticalNotifyCompletion
|
||||||
|
where TStateMachine : IAsyncStateMachine
|
||||||
|
=> StateMachineBox<TStateMachine>.AwaitUnsafeOnCompleted(ref awaiter, ref stateMachine);
|
||||||
|
|
||||||
|
[Browsable(false)]
|
||||||
|
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public void Start<TStateMachine>(ref TStateMachine stateMachine)
|
||||||
|
where TStateMachine : IAsyncStateMachine => stateMachine.MoveNext();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,105 @@
|
|||||||
|
using PooledAwait.Internal;
|
||||||
|
|
||||||
|
using System.ComponentModel;
|
||||||
|
using System.Runtime.CompilerServices;
|
||||||
|
|
||||||
|
using SystemTask = System.Threading.Tasks.Task;
|
||||||
|
|
||||||
|
#pragma warning disable CS1591
|
||||||
|
|
||||||
|
namespace PooledAwait.MethodBuilders
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// This type is not intended for direct usage
|
||||||
|
/// </summary>
|
||||||
|
[Browsable(false)]
|
||||||
|
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||||
|
public struct PooledTaskMethodBuilder
|
||||||
|
{
|
||||||
|
public override bool Equals(object? obj) => ThrowHelper.ThrowNotSupportedException<bool>();
|
||||||
|
public override int GetHashCode() => ThrowHelper.ThrowNotSupportedException<int>();
|
||||||
|
public override string ToString() => nameof(PooledTaskMethodBuilder);
|
||||||
|
|
||||||
|
private ValueTaskCompletionSource<Nothing> _source;
|
||||||
|
private Exception _exception;
|
||||||
|
|
||||||
|
[Browsable(false)]
|
||||||
|
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public static PooledTaskMethodBuilder Create() => default;
|
||||||
|
|
||||||
|
[Browsable(false)]
|
||||||
|
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public void SetStateMachine(IAsyncStateMachine _) => Counters.SetStateMachine.Increment();
|
||||||
|
|
||||||
|
[Browsable(false)]
|
||||||
|
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public void SetResult()
|
||||||
|
{
|
||||||
|
_source.TrySetResult(default);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Browsable(false)]
|
||||||
|
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public void SetException(Exception exception)
|
||||||
|
{
|
||||||
|
_source.TrySetException(exception);
|
||||||
|
_exception = exception;
|
||||||
|
}
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
private void EnsureHasTask()
|
||||||
|
{
|
||||||
|
if (_source.IsNull) _source = ValueTaskCompletionSource<Nothing>.Create();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Browsable(false)]
|
||||||
|
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||||
|
public PooledTask Task
|
||||||
|
{
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
get
|
||||||
|
{
|
||||||
|
SystemTask task;
|
||||||
|
if (!_source.IsNull) task = _source.Task;
|
||||||
|
else if (_exception is OperationCanceledException) task = TaskUtils.TaskFactory<Nothing>.Canceled;
|
||||||
|
else if (_exception != null) task = TaskUtils.FromException(_exception);
|
||||||
|
else task = TaskUtils.CompletedTask;
|
||||||
|
return new PooledTask(task);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Browsable(false)]
|
||||||
|
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public void AwaitOnCompleted<TAwaiter, TStateMachine>(
|
||||||
|
ref TAwaiter awaiter, ref TStateMachine stateMachine)
|
||||||
|
where TAwaiter : INotifyCompletion
|
||||||
|
where TStateMachine : IAsyncStateMachine
|
||||||
|
{
|
||||||
|
EnsureHasTask();
|
||||||
|
StateMachineBox<TStateMachine>.AwaitOnCompleted(ref awaiter, ref stateMachine);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Browsable(false)]
|
||||||
|
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public void AwaitUnsafeOnCompleted<TAwaiter, TStateMachine>(
|
||||||
|
ref TAwaiter awaiter, ref TStateMachine stateMachine)
|
||||||
|
where TAwaiter : ICriticalNotifyCompletion
|
||||||
|
where TStateMachine : IAsyncStateMachine
|
||||||
|
{
|
||||||
|
EnsureHasTask();
|
||||||
|
StateMachineBox<TStateMachine>.AwaitUnsafeOnCompleted(ref awaiter, ref stateMachine);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Browsable(false)]
|
||||||
|
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public void Start<TStateMachine>(ref TStateMachine stateMachine)
|
||||||
|
where TStateMachine : IAsyncStateMachine => stateMachine.MoveNext();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,105 @@
|
|||||||
|
using PooledAwait.Internal;
|
||||||
|
|
||||||
|
using System.ComponentModel;
|
||||||
|
using System.Runtime.CompilerServices;
|
||||||
|
|
||||||
|
#pragma warning disable CS1591
|
||||||
|
|
||||||
|
namespace PooledAwait.MethodBuilders
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// This type is not intended for direct usage
|
||||||
|
/// </summary>
|
||||||
|
[Browsable(false)]
|
||||||
|
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||||
|
public struct PooledTaskMethodBuilder<T>
|
||||||
|
{
|
||||||
|
public override bool Equals(object? obj) => ThrowHelper.ThrowNotSupportedException<bool>();
|
||||||
|
public override int GetHashCode() => ThrowHelper.ThrowNotSupportedException<int>();
|
||||||
|
public override string ToString() => nameof(PooledTaskMethodBuilder);
|
||||||
|
|
||||||
|
private ValueTaskCompletionSource<T> _source;
|
||||||
|
private Exception _exception;
|
||||||
|
private T _result;
|
||||||
|
|
||||||
|
[Browsable(false)]
|
||||||
|
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public static PooledTaskMethodBuilder<T> Create() => default;
|
||||||
|
|
||||||
|
[Browsable(false)]
|
||||||
|
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public void SetStateMachine(IAsyncStateMachine _) => Counters.SetStateMachine.Increment();
|
||||||
|
|
||||||
|
[Browsable(false)]
|
||||||
|
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public void SetResult(T result)
|
||||||
|
{
|
||||||
|
_source.TrySetResult(result);
|
||||||
|
_result = result;
|
||||||
|
}
|
||||||
|
|
||||||
|
[Browsable(false)]
|
||||||
|
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public void SetException(Exception exception)
|
||||||
|
{
|
||||||
|
_source.TrySetException(exception);
|
||||||
|
_exception = exception;
|
||||||
|
}
|
||||||
|
|
||||||
|
[Browsable(false)]
|
||||||
|
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||||
|
public PooledTask<T> Task
|
||||||
|
{
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
get
|
||||||
|
{
|
||||||
|
Task<T> task;
|
||||||
|
if (!_source.IsNull) task = _source.Task;
|
||||||
|
else if (_exception is OperationCanceledException) task = TaskUtils.TaskFactory<T>.Canceled;
|
||||||
|
else if (_exception != null) task = TaskUtils.FromException<T>(_exception);
|
||||||
|
else task = TaskUtils.TaskFactory<T>.FromResult(_result);
|
||||||
|
return new PooledTask<T>(task);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
private void EnsureHasTask()
|
||||||
|
{
|
||||||
|
if (_source.IsNull) _source = ValueTaskCompletionSource<T>.Create();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Browsable(false)]
|
||||||
|
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public void AwaitOnCompleted<TAwaiter, TStateMachine>(
|
||||||
|
ref TAwaiter awaiter, ref TStateMachine stateMachine)
|
||||||
|
where TAwaiter : INotifyCompletion
|
||||||
|
where TStateMachine : IAsyncStateMachine
|
||||||
|
{
|
||||||
|
EnsureHasTask();
|
||||||
|
StateMachineBox<TStateMachine>.AwaitOnCompleted(ref awaiter, ref stateMachine);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Browsable(false)]
|
||||||
|
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public void AwaitUnsafeOnCompleted<TAwaiter, TStateMachine>(
|
||||||
|
ref TAwaiter awaiter, ref TStateMachine stateMachine)
|
||||||
|
where TAwaiter : ICriticalNotifyCompletion
|
||||||
|
where TStateMachine : IAsyncStateMachine
|
||||||
|
{
|
||||||
|
EnsureHasTask();
|
||||||
|
StateMachineBox<TStateMachine>.AwaitUnsafeOnCompleted(ref awaiter, ref stateMachine);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Browsable(false)]
|
||||||
|
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public void Start<TStateMachine>(ref TStateMachine stateMachine)
|
||||||
|
where TStateMachine : IAsyncStateMachine => stateMachine.MoveNext();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,94 @@
|
|||||||
|
using PooledAwait.Internal;
|
||||||
|
|
||||||
|
using System.ComponentModel;
|
||||||
|
using System.Runtime.CompilerServices;
|
||||||
|
|
||||||
|
#pragma warning disable CS1591
|
||||||
|
|
||||||
|
namespace PooledAwait.MethodBuilders
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// This type is not intended for direct usage
|
||||||
|
/// </summary>
|
||||||
|
[Browsable(false)]
|
||||||
|
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||||
|
public struct PooledValueTaskMethodBuilder
|
||||||
|
{
|
||||||
|
public override bool Equals(object? obj) => ThrowHelper.ThrowNotSupportedException<bool>();
|
||||||
|
public override int GetHashCode() => ThrowHelper.ThrowNotSupportedException<int>();
|
||||||
|
public override string ToString() => nameof(PooledValueTaskMethodBuilder);
|
||||||
|
|
||||||
|
private PooledValueTaskSource _source;
|
||||||
|
|
||||||
|
[Browsable(false)]
|
||||||
|
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public static PooledValueTaskMethodBuilder Create() => default;
|
||||||
|
|
||||||
|
[Browsable(false)]
|
||||||
|
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public void SetStateMachine(IAsyncStateMachine _) => Counters.SetStateMachine.Increment();
|
||||||
|
|
||||||
|
[Browsable(false)]
|
||||||
|
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public void SetResult()
|
||||||
|
{
|
||||||
|
_source.TrySetResult();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Browsable(false)]
|
||||||
|
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public void SetException(Exception exception)
|
||||||
|
{
|
||||||
|
EnsureHasTask();
|
||||||
|
_source.TrySetException(exception);
|
||||||
|
}
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
private void EnsureHasTask()
|
||||||
|
{
|
||||||
|
if (!_source.HasTask) _source = PooledValueTaskSource.Create();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Browsable(false)]
|
||||||
|
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||||
|
public PooledValueTask Task
|
||||||
|
{
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
get => _source.PooledTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
[Browsable(false)]
|
||||||
|
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public void AwaitOnCompleted<TAwaiter, TStateMachine>(
|
||||||
|
ref TAwaiter awaiter, ref TStateMachine stateMachine)
|
||||||
|
where TAwaiter : INotifyCompletion
|
||||||
|
where TStateMachine : IAsyncStateMachine
|
||||||
|
{
|
||||||
|
EnsureHasTask();
|
||||||
|
StateMachineBox<TStateMachine>.AwaitOnCompleted(ref awaiter, ref stateMachine);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Browsable(false)]
|
||||||
|
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public void AwaitUnsafeOnCompleted<TAwaiter, TStateMachine>(
|
||||||
|
ref TAwaiter awaiter, ref TStateMachine stateMachine)
|
||||||
|
where TAwaiter : ICriticalNotifyCompletion
|
||||||
|
where TStateMachine : IAsyncStateMachine
|
||||||
|
{
|
||||||
|
EnsureHasTask();
|
||||||
|
StateMachineBox<TStateMachine>.AwaitUnsafeOnCompleted(ref awaiter, ref stateMachine);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Browsable(false)]
|
||||||
|
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public void Start<TStateMachine>(ref TStateMachine stateMachine)
|
||||||
|
where TStateMachine : IAsyncStateMachine => stateMachine.MoveNext();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,95 @@
|
|||||||
|
using PooledAwait.Internal;
|
||||||
|
|
||||||
|
using System.ComponentModel;
|
||||||
|
using System.Runtime.CompilerServices;
|
||||||
|
|
||||||
|
#pragma warning disable CS1591
|
||||||
|
|
||||||
|
namespace PooledAwait.MethodBuilders
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// This type is not intended for direct usage
|
||||||
|
/// </summary>
|
||||||
|
[Browsable(false)]
|
||||||
|
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||||
|
public struct PooledValueTaskMethodBuilder<T>
|
||||||
|
{
|
||||||
|
public override bool Equals(object? obj) => ThrowHelper.ThrowNotSupportedException<bool>();
|
||||||
|
public override int GetHashCode() => ThrowHelper.ThrowNotSupportedException<int>();
|
||||||
|
public override string ToString() => nameof(PooledValueTaskMethodBuilder);
|
||||||
|
|
||||||
|
private PooledValueTaskSource<T> _source;
|
||||||
|
|
||||||
|
[Browsable(false)]
|
||||||
|
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public static PooledValueTaskMethodBuilder<T> Create() => default;
|
||||||
|
|
||||||
|
[Browsable(false)]
|
||||||
|
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public void SetStateMachine(IAsyncStateMachine _) => Counters.SetStateMachine.Increment();
|
||||||
|
|
||||||
|
[Browsable(false)]
|
||||||
|
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public void SetResult(T result)
|
||||||
|
{
|
||||||
|
if (_source.HasTask) _source.TrySetResult(result);
|
||||||
|
else _source = new PooledValueTaskSource<T>(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Browsable(false)]
|
||||||
|
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public void SetException(Exception exception)
|
||||||
|
{
|
||||||
|
EnsureHasTask();
|
||||||
|
_source.TrySetException(exception);
|
||||||
|
}
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
private void EnsureHasTask()
|
||||||
|
{
|
||||||
|
if (!_source.HasTask) _source = PooledValueTaskSource<T>.Create();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Browsable(false)]
|
||||||
|
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||||
|
public PooledValueTask<T> Task
|
||||||
|
{
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
get => _source.PooledTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
[Browsable(false)]
|
||||||
|
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public void AwaitOnCompleted<TAwaiter, TStateMachine>(
|
||||||
|
ref TAwaiter awaiter, ref TStateMachine stateMachine)
|
||||||
|
where TAwaiter : INotifyCompletion
|
||||||
|
where TStateMachine : IAsyncStateMachine
|
||||||
|
{
|
||||||
|
EnsureHasTask();
|
||||||
|
StateMachineBox<TStateMachine>.AwaitOnCompleted(ref awaiter, ref stateMachine);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Browsable(false)]
|
||||||
|
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public void AwaitUnsafeOnCompleted<TAwaiter, TStateMachine>(
|
||||||
|
ref TAwaiter awaiter, ref TStateMachine stateMachine)
|
||||||
|
where TAwaiter : ICriticalNotifyCompletion
|
||||||
|
where TStateMachine : IAsyncStateMachine
|
||||||
|
{
|
||||||
|
EnsureHasTask();
|
||||||
|
StateMachineBox<TStateMachine>.AwaitUnsafeOnCompleted(ref awaiter, ref stateMachine);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Browsable(false)]
|
||||||
|
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public void Start<TStateMachine>(ref TStateMachine stateMachine)
|
||||||
|
where TStateMachine : IAsyncStateMachine => stateMachine.MoveNext();
|
||||||
|
}
|
||||||
|
}
|
||||||
73
src/Admin/ThingsGateway.NewLife.X/PooledAwait/Pool.cs
Normal file
73
src/Admin/ThingsGateway.NewLife.X/PooledAwait/Pool.cs
Normal file
@@ -0,0 +1,73 @@
|
|||||||
|
using PooledAwait.Internal;
|
||||||
|
|
||||||
|
using System.Runtime.CompilerServices;
|
||||||
|
|
||||||
|
namespace PooledAwait
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Utility methods for boxing value types efficiently, in particular for
|
||||||
|
/// avoid boxes and capture contexts in callbacks
|
||||||
|
/// </summary>
|
||||||
|
public static class Pool
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Gets an instance from the pool if possible
|
||||||
|
/// </summary>
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public static T? TryRent<T>() where T : class
|
||||||
|
=> Pool<T>.TryGet();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Puts an instance back into the pool
|
||||||
|
/// </summary>
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public static void Return<T>(T value) where T : class
|
||||||
|
{
|
||||||
|
if (value is IResettable reset) reset.Reset();
|
||||||
|
Pool<T>.TryPut(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Wraps a value-type into a boxed instance, using an object pool;
|
||||||
|
/// consider using value-tuples in particular
|
||||||
|
/// </summary>
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public static object Box<T>(in T value) where T : struct
|
||||||
|
=> ItemBox<T>.Create(in value);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Unwraps a value-type from a boxed instance and recycles
|
||||||
|
/// the instance, which should not be touched again
|
||||||
|
/// </summary>
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public static T UnboxAndReturn<T>(object obj) where T : struct
|
||||||
|
=> ItemBox<T>.UnboxAndRecycle(obj);
|
||||||
|
|
||||||
|
internal sealed class ItemBox<T> where T : struct
|
||||||
|
{
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
private ItemBox() => Counters.ItemBoxAllocated.Increment();
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public static ItemBox<T> Create(in T value)
|
||||||
|
{
|
||||||
|
var box = Pool<ItemBox<T>>.TryGet() ?? new ItemBox<T>();
|
||||||
|
box._value = value;
|
||||||
|
return box;
|
||||||
|
}
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public static T UnboxAndRecycle(object obj)
|
||||||
|
{
|
||||||
|
var box = (ItemBox<T>)obj;
|
||||||
|
var value = box._value;
|
||||||
|
box._value = default;
|
||||||
|
Pool<ItemBox<T>>.TryPut(box);
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
#pragma warning disable IDE0044 // make field readonly? no, IDE, you're wrong
|
||||||
|
private T _value;
|
||||||
|
#pragma warning restore IDE0044
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
#if !NETSTANDARD1_3
|
||||||
|
namespace PooledAwait
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Controls the number of elements to store in the pool
|
||||||
|
/// </summary>
|
||||||
|
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct, AllowMultiple = false, Inherited = true)]
|
||||||
|
public sealed class PoolSizeAttribute : Attribute
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The number of elements to store in the pool
|
||||||
|
/// </summary>
|
||||||
|
public int Size { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Create a new PoolSizeAttribute instance
|
||||||
|
/// </summary>
|
||||||
|
public PoolSizeAttribute(int size) => Size = size;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
37
src/Admin/ThingsGateway.NewLife.X/PooledAwait/PoolT.cs
Normal file
37
src/Admin/ThingsGateway.NewLife.X/PooledAwait/PoolT.cs
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
using System.Runtime.CompilerServices;
|
||||||
|
|
||||||
|
using ThingsGateway.NewLife.Collections;
|
||||||
|
|
||||||
|
namespace PooledAwait
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// A general-purpose pool of object references; it is the caller's responsibility
|
||||||
|
/// to ensure that overlapped usage does not occur
|
||||||
|
/// </summary>
|
||||||
|
internal static class Pool<T> where T : class
|
||||||
|
{
|
||||||
|
private static ObjectPoolLock<T> pool = new();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets an instance from the pool if possible
|
||||||
|
/// </summary>
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public static T? TryGet()
|
||||||
|
{
|
||||||
|
return pool.Get();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Puts an instance back into the pool
|
||||||
|
/// </summary>
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public static void TryPut(T value)
|
||||||
|
{
|
||||||
|
if (value != null)
|
||||||
|
{
|
||||||
|
pool.Return(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
<Import Project="..\..\Foundation.props" />
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<IncludeAsyncInterfaces>false</IncludeAsyncInterfaces>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<PropertyGroup Condition="$(TargetFramework)=='net6.0'">
|
||||||
|
<DefineConstants>$(DefineConstants);PLAT_THREADPOOLWORKITEM;PLAT_MRVTSC</DefineConstants>
|
||||||
|
</PropertyGroup>
|
||||||
|
<PropertyGroup Condition="$(TargetFramework)=='net8.0'">
|
||||||
|
<DefineConstants>$(DefineConstants);PLAT_THREADPOOLWORKITEM;PLAT_MRVTSC</DefineConstants>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<PropertyGroup Condition="$(TargetFramework)=='netstandard2.0'">
|
||||||
|
<DefineConstants>$(DefineConstants);PLAT_MRVTSC</DefineConstants>
|
||||||
|
<IncludeAsyncInterfaces>true</IncludeAsyncInterfaces>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<PropertyGroup Condition="$(TargetFramework)=='net462'">
|
||||||
|
<DefineConstants>$(DefineConstants);PLAT_MRVTSC</DefineConstants>
|
||||||
|
<IncludeAsyncInterfaces>true</IncludeAsyncInterfaces>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="Microsoft.Bcl.AsyncInterfaces" Version="1.1.0" Condition="$(IncludeAsyncInterfaces)=='true'" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
||||||
60
src/Admin/ThingsGateway.NewLife.X/PooledAwait/PooledTask.cs
Normal file
60
src/Admin/ThingsGateway.NewLife.X/PooledAwait/PooledTask.cs
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
using PooledAwait.Internal;
|
||||||
|
|
||||||
|
using System.Runtime.CompilerServices;
|
||||||
|
|
||||||
|
namespace PooledAwait
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// A Task, but with a custom builder
|
||||||
|
/// </summary>
|
||||||
|
[AsyncMethodBuilder(typeof(MethodBuilders.PooledTaskMethodBuilder))]
|
||||||
|
public readonly struct PooledTask
|
||||||
|
{
|
||||||
|
/// <summary><see cref="Object.Equals(Object)"/></summary>
|
||||||
|
public override bool Equals(object? obj) => obj is PooledTask pt && _task == pt._task;
|
||||||
|
/// <summary><see cref="Object.GetHashCode"/></summary>
|
||||||
|
public override int GetHashCode() => _task == null ? 0 : _task.GetHashCode();
|
||||||
|
/// <summary><see cref="Object.ToString"/></summary>
|
||||||
|
public override string ToString() => nameof(PooledTask);
|
||||||
|
|
||||||
|
private readonly Task _task;
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
internal PooledTask(Task task) => _task = task;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the instance as a task
|
||||||
|
/// </summary>
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public Task AsTask() => _task ?? TaskUtils.CompletedTask;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the instance as a task
|
||||||
|
/// </summary>
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
|
||||||
|
public static implicit operator Task(in PooledTask task) => task.AsTask();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the awaiter for the task
|
||||||
|
/// </summary>
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public TaskAwaiter GetAwaiter() => AsTask().GetAwaiter();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the configured awaiter for the task
|
||||||
|
/// </summary>
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public ConfiguredTaskAwaitable ConfigureAwait(bool continueOnCapturedContext)
|
||||||
|
=> AsTask().ConfigureAwait(continueOnCapturedContext);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Indicates whether this is an invalid default instance
|
||||||
|
/// </summary>
|
||||||
|
public bool IsNull
|
||||||
|
{
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
get => _task == null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
60
src/Admin/ThingsGateway.NewLife.X/PooledAwait/PooledTaskT.cs
Normal file
60
src/Admin/ThingsGateway.NewLife.X/PooledAwait/PooledTaskT.cs
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
using PooledAwait.Internal;
|
||||||
|
|
||||||
|
using System.Runtime.CompilerServices;
|
||||||
|
|
||||||
|
namespace PooledAwait
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// A Task<typeparamref name="T"/>, but with a custom builder
|
||||||
|
/// </summary>
|
||||||
|
[AsyncMethodBuilder(typeof(MethodBuilders.PooledTaskMethodBuilder<>))]
|
||||||
|
public readonly struct PooledTask<T>
|
||||||
|
{
|
||||||
|
/// <summary><see cref="Object.Equals(Object)"/></summary>
|
||||||
|
public override bool Equals(object? obj) => obj is PooledTask<T> pt && _task == pt._task;
|
||||||
|
/// <summary><see cref="Object.GetHashCode"/></summary>
|
||||||
|
public override int GetHashCode() => _task == null ? 0 : _task.GetHashCode();
|
||||||
|
/// <summary><see cref="Object.ToString"/></summary>
|
||||||
|
public override string ToString() => nameof(PooledTask);
|
||||||
|
|
||||||
|
private readonly Task<T>? _task;
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
internal PooledTask(Task<T> task) => _task = task;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the instance as a task
|
||||||
|
/// </summary>
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public Task<T> AsTask() => _task ?? ThrowHelper.ThrowInvalidOperationException<Task<T>>();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the instance as a task
|
||||||
|
/// </summary>
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
|
||||||
|
public static implicit operator Task<T>(in PooledTask<T> task) => task.AsTask();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the awaiter for the task
|
||||||
|
/// </summary>
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public TaskAwaiter<T> GetAwaiter() => AsTask().GetAwaiter();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the configured awaiter for the task
|
||||||
|
/// </summary>
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public ConfiguredTaskAwaitable<T> ConfigureAwait(bool continueOnCapturedContext)
|
||||||
|
=> AsTask().ConfigureAwait(continueOnCapturedContext);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Indicates whether this is an invalid default instance
|
||||||
|
/// </summary>
|
||||||
|
public bool IsNull
|
||||||
|
{
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
get => _task == null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,71 @@
|
|||||||
|
using System.Runtime.CompilerServices;
|
||||||
|
using System.Threading.Tasks.Sources;
|
||||||
|
|
||||||
|
namespace PooledAwait
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// A ValueTask with a custom source and builder
|
||||||
|
/// </summary>
|
||||||
|
[AsyncMethodBuilder(typeof(MethodBuilders.PooledValueTaskMethodBuilder))]
|
||||||
|
public readonly struct PooledValueTask
|
||||||
|
{
|
||||||
|
/// <summary><see cref="Object.Equals(Object)"/></summary>
|
||||||
|
public override bool Equals(object? obj) => obj is PooledValueTask pvt && _source == pvt._source && _token == pvt._token;
|
||||||
|
/// <summary><see cref="Object.GetHashCode"/></summary>
|
||||||
|
public override int GetHashCode() => (_source == null ? 0 : _source.GetHashCode()) ^ _token;
|
||||||
|
/// <summary><see cref="Object.ToString"/></summary>
|
||||||
|
public override string ToString() => nameof(PooledValueTask);
|
||||||
|
|
||||||
|
private readonly IValueTaskSource _source;
|
||||||
|
private readonly short _token;
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
internal PooledValueTask(IValueTaskSource source, short token)
|
||||||
|
{
|
||||||
|
_source = source;
|
||||||
|
_token = token;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the instance as a value-task
|
||||||
|
/// </summary>
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public ValueTask AsValueTask() => _source == null ? default : new ValueTask(_source, _token);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the instance as a value-task
|
||||||
|
/// </summary>
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public static implicit operator ValueTask(in PooledValueTask task) => task.AsValueTask();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the awaiter for the task
|
||||||
|
/// </summary>
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
#pragma warning disable CA2012 // 正确使用 ValueTask
|
||||||
|
public ValueTaskAwaiter GetAwaiter() => AsValueTask().GetAwaiter();
|
||||||
|
#pragma warning restore CA2012 // 正确使用 ValueTask
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the configured awaiter for the task
|
||||||
|
/// </summary>
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public ConfiguredValueTaskAwaitable ConfigureAwait(bool continueOnCapturedContext)
|
||||||
|
=> AsValueTask().ConfigureAwait(continueOnCapturedContext);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Rents a task-source that will be recycled when the task is awaited
|
||||||
|
/// </summary>
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
internal static PooledValueTaskSource CreateSource() => PooledValueTaskSource.Create();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Indicates whether this is an invalid default instance
|
||||||
|
/// </summary>
|
||||||
|
public bool IsNull
|
||||||
|
{
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
get => _source == null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,126 @@
|
|||||||
|
using PooledAwait.Internal;
|
||||||
|
|
||||||
|
using System.Runtime.CompilerServices;
|
||||||
|
|
||||||
|
namespace PooledAwait
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// A task-source that automatically recycles when the task is awaited
|
||||||
|
/// </summary>
|
||||||
|
public readonly struct PooledValueTaskSource
|
||||||
|
{
|
||||||
|
/// <summary><see cref="Object.Equals(Object)"/></summary>
|
||||||
|
public override bool Equals(object? obj) => obj is PooledValueTaskSource pvt && _source == pvt._source && _token == pvt._token;
|
||||||
|
/// <summary><see cref="Object.GetHashCode"/></summary>
|
||||||
|
public override int GetHashCode() => (_source == null ? 0 : _source.GetHashCode()) ^ _token;
|
||||||
|
/// <summary><see cref="Object.ToString"/></summary>
|
||||||
|
public override string ToString() => nameof(PooledValueTaskSource);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the task that corresponds to this instance; it can only be awaited once
|
||||||
|
/// </summary>
|
||||||
|
public ValueTask Task
|
||||||
|
{
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
get => _source == null ? default : new ValueTask(_source, _token);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Indicates whether this instance is well-defined against a value task instance
|
||||||
|
/// </summary>
|
||||||
|
public bool HasTask
|
||||||
|
{
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
get => _source != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal PooledValueTask PooledTask
|
||||||
|
{
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
get => new PooledValueTask(_source, _token);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Rents a task-source that will be recycled when the task is awaited
|
||||||
|
/// </summary>
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public static PooledValueTaskSource Create()
|
||||||
|
{
|
||||||
|
var source = PooledState<Nothing>.Create(out var token);
|
||||||
|
return new PooledValueTaskSource(source, token);
|
||||||
|
}
|
||||||
|
|
||||||
|
private readonly PooledState<Nothing> _source;
|
||||||
|
private readonly short _token;
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
internal PooledValueTaskSource(PooledState<Nothing> source, short token)
|
||||||
|
{
|
||||||
|
_source = source;
|
||||||
|
_token = token;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Test whether the source is valid
|
||||||
|
/// </summary>
|
||||||
|
public bool IsValid
|
||||||
|
{
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
get => _source?.IsValid(_token) == true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Set the result of the operation
|
||||||
|
/// </summary>
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public bool TrySetResult() => _source?.TrySetResult(default, _token) == true;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Set the result of the operation
|
||||||
|
/// </summary>
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public void SetResult()
|
||||||
|
{
|
||||||
|
if (!TrySetResult()) ThrowHelper.ThrowInvalidOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Set the result of the operation
|
||||||
|
/// </summary>
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public bool TrySetException(Exception error) => _source?.TrySetException(error, _token) == true;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Set the result of the operation
|
||||||
|
/// </summary>
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public void SetException(Exception exception)
|
||||||
|
{
|
||||||
|
if (!TrySetException(exception)) ThrowHelper.ThrowInvalidOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Set the result of the operation
|
||||||
|
/// </summary>
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public bool TrySetCanceled() => _source?.TrySetCanceled(_token) == true;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Set the result of the operation
|
||||||
|
/// </summary>
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public void SetCanceled()
|
||||||
|
{
|
||||||
|
if (!TrySetCanceled()) ThrowHelper.ThrowInvalidOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Indicates whether this is an invalid default instance
|
||||||
|
/// </summary>
|
||||||
|
public bool IsNull
|
||||||
|
{
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
get => _source == null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,141 @@
|
|||||||
|
using PooledAwait.Internal;
|
||||||
|
|
||||||
|
using System.Runtime.CompilerServices;
|
||||||
|
|
||||||
|
namespace PooledAwait
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// A task-source that automatically recycles when the task is awaited
|
||||||
|
/// </summary>
|
||||||
|
public readonly struct PooledValueTaskSource<T>
|
||||||
|
{
|
||||||
|
/// <summary><see cref="Object.Equals(Object)"/></summary>
|
||||||
|
public override bool Equals(object? obj) => obj is PooledValueTaskSource<T> pvt && _token == pvt._token &&
|
||||||
|
(_source != null ? _source == pvt._source : (pvt._source == null && EqualityComparer<T>.Default.Equals(_value, pvt._value)));
|
||||||
|
/// <summary><see cref="Object.GetHashCode"/></summary>
|
||||||
|
public override int GetHashCode() => (_source == null ? EqualityComparer<T>.Default.GetHashCode(_value!) : _source.GetHashCode()) ^ _token;
|
||||||
|
/// <summary><see cref="Object.ToString"/></summary>
|
||||||
|
public override string ToString() => nameof(PooledValueTaskSource);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the task that corresponds to this instance; it can only be awaited once
|
||||||
|
/// </summary>
|
||||||
|
public ValueTask<T> Task
|
||||||
|
{
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
get => _source == null ? new ValueTask<T>(_value) : new ValueTask<T>(_source, _token);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Indicates whether this instance is well-defined against a value task instance
|
||||||
|
/// </summary>
|
||||||
|
public bool HasTask
|
||||||
|
{
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
get => _source != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal PooledValueTask<T> PooledTask
|
||||||
|
{
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
get => _source == null ? new PooledValueTask<T>(_value) : new PooledValueTask<T>(_source, _token);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Rents a task-source that will be recycled when the task is awaited
|
||||||
|
/// </summary>
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public static PooledValueTaskSource<T> Create()
|
||||||
|
{
|
||||||
|
var source = PooledState<T>.Create(out var token);
|
||||||
|
return new PooledValueTaskSource<T>(source, token);
|
||||||
|
}
|
||||||
|
|
||||||
|
private readonly PooledState<T>? _source;
|
||||||
|
private readonly short _token;
|
||||||
|
|
||||||
|
private readonly T _value;
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
internal PooledValueTaskSource(PooledState<T> source, short token)
|
||||||
|
{
|
||||||
|
_source = source;
|
||||||
|
_token = token;
|
||||||
|
_value = default!;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Create a new PooledValueTaskSource that will yield a constant value without ever renting/recycling any background state
|
||||||
|
/// </summary>
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public PooledValueTaskSource(T value)
|
||||||
|
{
|
||||||
|
_source = null;
|
||||||
|
_token = default;
|
||||||
|
_value = value!;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Test whether the source is valid
|
||||||
|
/// </summary>
|
||||||
|
public bool IsValid
|
||||||
|
{
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
get => _source?.IsValid(_token) == true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Set the result of the operation
|
||||||
|
/// </summary>
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public bool TrySetResult(T result) => _source?.TrySetResult(result, _token) == true;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Set the result of the operation
|
||||||
|
/// </summary>
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public void SetResult(T result)
|
||||||
|
{
|
||||||
|
if (!TrySetResult(result)) ThrowHelper.ThrowInvalidOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Set the result of the operation
|
||||||
|
/// </summary>
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public bool TrySetException(Exception error) => _source?.TrySetException(error, _token) == true;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Set the result of the operation
|
||||||
|
/// </summary>
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public void SetException(Exception exception)
|
||||||
|
{
|
||||||
|
if (!TrySetException(exception)) ThrowHelper.ThrowInvalidOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Set the result of the operation
|
||||||
|
/// </summary>
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public bool TrySetCanceled() => _source?.TrySetCanceled(_token) == true;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Set the result of the operation
|
||||||
|
/// </summary>
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public void SetCanceled()
|
||||||
|
{
|
||||||
|
if (!TrySetCanceled()) ThrowHelper.ThrowInvalidOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Indicates whether this is an invalid default instance
|
||||||
|
/// </summary>
|
||||||
|
public bool IsNull
|
||||||
|
{
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
get => _source == null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,85 @@
|
|||||||
|
using PooledAwait.Internal;
|
||||||
|
|
||||||
|
using System.Runtime.CompilerServices;
|
||||||
|
|
||||||
|
namespace PooledAwait
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// A ValueTask<typeparamref name="T"/> with a custom source and builder
|
||||||
|
/// </summary>
|
||||||
|
[AsyncMethodBuilder(typeof(MethodBuilders.PooledValueTaskMethodBuilder<>))]
|
||||||
|
public readonly struct PooledValueTask<T>
|
||||||
|
{
|
||||||
|
/// <summary><see cref="Object.Equals(Object)"/></summary>
|
||||||
|
public override bool Equals(object? obj) => obj is PooledValueTask<T> pvt && _source == pvt._source && _token == pvt._token;
|
||||||
|
/// <summary><see cref="Object.GetHashCode"/></summary>
|
||||||
|
public override int GetHashCode() => (_source == null ? 0 : _source.GetHashCode()) ^ _token;
|
||||||
|
/// <summary><see cref="Object.ToString"/></summary>
|
||||||
|
public override string ToString() => nameof(PooledValueTask);
|
||||||
|
|
||||||
|
private readonly PooledState<T>? _source;
|
||||||
|
private readonly short _token;
|
||||||
|
private readonly T _result;
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
internal PooledValueTask(PooledState<T> source, short token)
|
||||||
|
{
|
||||||
|
_source = source;
|
||||||
|
_token = token;
|
||||||
|
_result = default!;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a value-task with a fixed value
|
||||||
|
/// </summary>
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public PooledValueTask(T result)
|
||||||
|
{
|
||||||
|
_source = default;
|
||||||
|
_token = default;
|
||||||
|
_result = result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the instance as a value-task
|
||||||
|
/// </summary>
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public ValueTask<T> AsValueTask() => _source == null ? new ValueTask<T>(_result) : new ValueTask<T>(_source, _token);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the instance as a value-task
|
||||||
|
/// </summary>
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public static implicit operator ValueTask<T>(in PooledValueTask<T> task) => task.AsValueTask();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the awaiter for the task
|
||||||
|
/// </summary>
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
#pragma warning disable CA2012 // 正确使用 ValueTask
|
||||||
|
public ValueTaskAwaiter<T> GetAwaiter() => AsValueTask().GetAwaiter();
|
||||||
|
#pragma warning restore CA2012 // 正确使用 ValueTask
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the configured awaiter for the task
|
||||||
|
/// </summary>
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public ConfiguredValueTaskAwaitable<T> ConfigureAwait(bool continueOnCapturedContext)
|
||||||
|
=> AsValueTask().ConfigureAwait(continueOnCapturedContext);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Rents a task-source that will be recycled when the task is awaited
|
||||||
|
/// </summary>
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
internal static PooledValueTaskSource<T> CreateSource() => PooledValueTaskSource<T>.Create();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Indicates whether this is an invalid default instance
|
||||||
|
/// </summary>
|
||||||
|
public bool IsNull
|
||||||
|
{
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
get => _source == null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
using System.Runtime.CompilerServices;
|
||||||
|
|
||||||
|
[assembly: InternalsVisibleTo("Benchmark, PublicKey=0024000004800000940000000602000000240000525341310004000001000100d136a87a0c11ded39332f7ead1f2ce53256a42a350ee847df1dc520bc781210a5f1fe73b3f91a4001fdcfa35340a212e70443de46e6928510f57a55f4ab9b95257f5067d4de4be8cdad460781866b20a6ca20f66302df61b52e55e16c080cad95aae8b94ffdd54718b9963d0240b0d0d5afe655b05c91771f7dc8a314387d3c3")]
|
||||||
|
|
||||||
|
[assembly: InternalsVisibleTo("PooledAwait.Test, PublicKey=0024000004800000940000000602000000240000525341310004000001000100d136a87a0c11ded39332f7ead1f2ce53256a42a350ee847df1dc520bc781210a5f1fe73b3f91a4001fdcfa35340a212e70443de46e6928510f57a55f4ab9b95257f5067d4de4be8cdad460781866b20a6ca20f66302df61b52e55e16c080cad95aae8b94ffdd54718b9963d0240b0d0d5afe655b05c91771f7dc8a314387d3c3")]
|
||||||
@@ -0,0 +1,218 @@
|
|||||||
|
using System.Runtime.CompilerServices;
|
||||||
|
|
||||||
|
using PooledAwait.Internal;
|
||||||
|
|
||||||
|
#if !NETSTANDARD1_3
|
||||||
|
using System.Reflection;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
namespace PooledAwait
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Lightweight implementation of TaskCompletionSource<typeparamref name="T"/>
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>When possible, this will bypass TaskCompletionSource<typeparamref name="T"/> completely</remarks>
|
||||||
|
public readonly struct ValueTaskCompletionSource<T>
|
||||||
|
{
|
||||||
|
/// <summary><see cref="Object.Equals(Object)"/></summary>
|
||||||
|
public override bool Equals(object? obj) => obj is ValueTaskCompletionSource<T> other && _state == other._state;
|
||||||
|
/// <summary><see cref="Object.GetHashCode"/></summary>
|
||||||
|
public override int GetHashCode() => _state == null ? 0 : _state.GetHashCode();
|
||||||
|
/// <summary><see cref="Object.ToString"/></summary>
|
||||||
|
public override string ToString() => "ValueTaskCompletionSource";
|
||||||
|
|
||||||
|
#if !NETSTANDARD1_3
|
||||||
|
private static readonly Func<Task<T>, Exception, bool>? s_TrySetException = TryCreate<Exception>(nameof(TrySetException));
|
||||||
|
private static readonly Func<Task<T>, T, bool>? s_TrySetResult = TryCreate<T>(nameof(TrySetResult));
|
||||||
|
private static readonly Func<Task<T>, CancellationToken, bool>? s_TrySetCanceled = TryCreate<CancellationToken>(nameof(TrySetCanceled));
|
||||||
|
private static readonly bool s_Optimized = ValidateOptimized();
|
||||||
|
#endif
|
||||||
|
private readonly object _state;
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
private ValueTaskCompletionSource(object state) => _state = state;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the instance as a task
|
||||||
|
/// </summary>
|
||||||
|
public Task<T> Task
|
||||||
|
{
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
get => _state as Task<T> ?? ((TaskCompletionSource<T>)_state).Task;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Indicates whether this is an invalid default instance
|
||||||
|
/// </summary>
|
||||||
|
public bool IsNull
|
||||||
|
{
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
get => _state == null;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal bool IsOptimized => _state is Task<T>;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Create an instance pointing to a new task
|
||||||
|
/// </summary>
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public static ValueTaskCompletionSource<T> Create() =>
|
||||||
|
#if !NETSTANDARD1_3
|
||||||
|
s_Optimized ? CreateOptimized() :
|
||||||
|
#endif
|
||||||
|
CreateFallback();
|
||||||
|
|
||||||
|
#if !NETSTANDARD1_3
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
internal static ValueTaskCompletionSource<T> CreateOptimized()
|
||||||
|
{
|
||||||
|
Counters.TaskAllocated.Increment();
|
||||||
|
return new ValueTaskCompletionSource<T>(new TaskCompletionSource<T>(TaskCreationOptions.RunContinuationsAsynchronously));
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
internal static ValueTaskCompletionSource<T> CreateFallback()
|
||||||
|
{
|
||||||
|
Counters.TaskAllocated.Increment();
|
||||||
|
return new ValueTaskCompletionSource<T>(new TaskCompletionSource<T>());
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Set the outcome of the operation
|
||||||
|
/// </summary>
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public bool TrySetException(Exception exception)
|
||||||
|
{
|
||||||
|
#if !NETSTANDARD1_3
|
||||||
|
if (_state is Task<T> task)
|
||||||
|
{
|
||||||
|
var result = s_TrySetException!(task, exception);
|
||||||
|
if (!result && !task.IsCompleted) SpinUntilCompleted(task);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
return _state != null && ((TaskCompletionSource<T>)_state).TrySetException(exception);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Set the result of the operation
|
||||||
|
/// </summary>
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public void SetException(Exception exception)
|
||||||
|
{
|
||||||
|
if (!TrySetException(exception)) ThrowHelper.ThrowInvalidOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Set the outcome of the operation
|
||||||
|
/// </summary>
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public bool TrySetCanceled(CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
#if !NETSTANDARD1_3
|
||||||
|
if (_state is Task<T> task)
|
||||||
|
{
|
||||||
|
var result = s_TrySetCanceled!(task, cancellationToken);
|
||||||
|
if (!result && !task.IsCompleted) SpinUntilCompleted(task);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
return _state != null && ((TaskCompletionSource<T>)_state).TrySetCanceled(
|
||||||
|
#if !NET45
|
||||||
|
cancellationToken
|
||||||
|
#endif
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Set the result of the operation
|
||||||
|
/// </summary>
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public void SetCanceled(CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
if (!TrySetCanceled(cancellationToken)) ThrowHelper.ThrowInvalidOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Set the outcome of the operation
|
||||||
|
/// </summary>
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public bool TrySetResult(T value)
|
||||||
|
{
|
||||||
|
#if !NETSTANDARD1_3
|
||||||
|
if (_state is Task<T> task)
|
||||||
|
{
|
||||||
|
var result = s_TrySetResult!(task, value);
|
||||||
|
if (!result && !task.IsCompleted) SpinUntilCompleted(task);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
return _state != null && ((TaskCompletionSource<T>)_state).TrySetResult(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Set the result of the operation
|
||||||
|
/// </summary>
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public void SetResult(T value)
|
||||||
|
{
|
||||||
|
if (!TrySetResult(value)) ThrowHelper.ThrowInvalidOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
#if !NETSTANDARD1_3
|
||||||
|
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||||
|
private static Func<Task<T>, TArg, bool>? TryCreate<TArg>(string methodName)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return (Func<Task<T>, TArg, bool>)Delegate.CreateDelegate(
|
||||||
|
typeof(Func<Task<T>, TArg, bool>),
|
||||||
|
typeof(Task<T>).GetMethod(methodName, BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance,
|
||||||
|
null, new[] { typeof(TArg) }, null)!);
|
||||||
|
}
|
||||||
|
catch { return null; }
|
||||||
|
}
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||||
|
private static bool ValidateOptimized()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// perform feature tests of our voodoo
|
||||||
|
var source = CreateOptimized();
|
||||||
|
var task = source.Task;
|
||||||
|
if (task == null) return false;
|
||||||
|
if (task.IsCompleted) return false;
|
||||||
|
|
||||||
|
if (!source.TrySetResult(default!)) return false;
|
||||||
|
if (task.Status != TaskStatus.RanToCompletion) return false;
|
||||||
|
|
||||||
|
source = CreateOptimized();
|
||||||
|
task = source.Task;
|
||||||
|
if (!source.TrySetException(new InvalidOperationException())) return false;
|
||||||
|
if (!task.IsCompleted) return false;
|
||||||
|
if (!task.IsFaulted) return false;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
_ = task.Result;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
catch (AggregateException ex) when (ex.InnerException is InvalidOperationException) { }
|
||||||
|
if (!(task.Exception?.InnerException is InvalidOperationException)) return false;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
catch { return false; }
|
||||||
|
}
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||||
|
private void SpinUntilCompleted(Task<T> task)
|
||||||
|
{
|
||||||
|
// Spin wait until the completion is finalized by another thread.
|
||||||
|
var sw = new SpinWait();
|
||||||
|
while (!task.IsCompleted)
|
||||||
|
sw.SpinOnce();
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -580,7 +580,19 @@ public static class Reflect
|
|||||||
|
|
||||||
return func;
|
return func;
|
||||||
}
|
}
|
||||||
|
/// <summary>把一个方法转为泛型委托,便于快速反射调用</summary>
|
||||||
|
/// <typeparam name="TFunc"></typeparam>
|
||||||
|
/// <param name="method"></param>
|
||||||
|
/// <param name="target"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public static void RemoveCache<TFunc>(this MethodInfo method, object? target = null) where TFunc : class
|
||||||
|
{
|
||||||
|
if (method == null) return;
|
||||||
|
|
||||||
|
var key = new DelegateCacheKey(method, typeof(TFunc), target);
|
||||||
|
|
||||||
|
DelegateCache<TFunc>.Cache.TryRemove(key);
|
||||||
|
}
|
||||||
#endregion
|
#endregion
|
||||||
}
|
}
|
||||||
public static class DelegateCache<TFunc>
|
public static class DelegateCache<TFunc>
|
||||||
|
|||||||
@@ -24,7 +24,7 @@
|
|||||||
<None Remove="..\..\..\README.zh-CN.md" Pack="false" PackagePath="\" />
|
<None Remove="..\..\..\README.zh-CN.md" Pack="false" PackagePath="\" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<PropertyGroup Condition="'$(TargetFramework)'=='net462' or '$(TargetFramework)'=='net5.0-windows' or '$(TargetFramework)'=='net6.0-windows' or '$(TargetFramework)'=='net7.0-windows' or '$(TargetFramework)'=='net8.0-windows'">
|
<PropertyGroup Condition="'$(TargetFramework)'=='net47' or '$(TargetFramework)'=='net5.0-windows' or '$(TargetFramework)'=='net6.0-windows' or '$(TargetFramework)'=='net7.0-windows' or '$(TargetFramework)'=='net8.0-windows'">
|
||||||
<DefineConstants>__WIN__</DefineConstants>
|
<DefineConstants>__WIN__</DefineConstants>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
@@ -54,7 +54,26 @@
|
|||||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.4" />
|
<PackageReference Include="Newtonsoft.Json" Version="13.0.4" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
<PropertyGroup Condition="$(TargetFramework)=='net6.0'">
|
||||||
|
<DefineConstants>$(DefineConstants);PLAT_THREADPOOLWORKITEM;PLAT_MRVTSC</DefineConstants>
|
||||||
|
</PropertyGroup>
|
||||||
|
<PropertyGroup Condition="$(TargetFramework)=='net8.0'">
|
||||||
|
<DefineConstants>$(DefineConstants);PLAT_THREADPOOLWORKITEM;PLAT_MRVTSC</DefineConstants>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<PropertyGroup Condition="$(TargetFramework)=='netstandard2.0'">
|
||||||
|
<DefineConstants>$(DefineConstants);PLAT_MRVTSC</DefineConstants>
|
||||||
|
<IncludeAsyncInterfaces>true</IncludeAsyncInterfaces>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<PropertyGroup Condition="$(TargetFramework)=='net47'">
|
||||||
|
<DefineConstants>$(DefineConstants);PLAT_MRVTSC</DefineConstants>
|
||||||
|
<IncludeAsyncInterfaces>true</IncludeAsyncInterfaces>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="Microsoft.Bcl.AsyncInterfaces" Version="9.0.10" Condition="$(IncludeAsyncInterfaces)=='true'" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
<!--<ItemGroup Condition="'$(TargetFramework)'=='net6.0-windows' or '$(TargetFramework)'=='net6.0' ">
|
<!--<ItemGroup Condition="'$(TargetFramework)'=='net6.0-windows' or '$(TargetFramework)'=='net6.0' ">
|
||||||
<PackageReference Include="Microsoft.AspNetCore.DataProtection" Version="6.0.36" />
|
<PackageReference Include="Microsoft.AspNetCore.DataProtection" Version="6.0.36" />
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
using System.Diagnostics;
|
using PooledAwait;
|
||||||
|
|
||||||
using ThingsGateway.NewLife.Collections;
|
using ThingsGateway.NewLife.Collections;
|
||||||
using ThingsGateway.NewLife.Log;
|
using ThingsGateway.NewLife.Log;
|
||||||
@@ -191,6 +191,13 @@ public class TimerScheduler : IDisposable, ILogFeature
|
|||||||
Count--;
|
Count--;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
timer.Method.RemoveCache<TimerCallback>(timer.Target.Target);
|
||||||
|
#if NET6_0_OR_GREATER
|
||||||
|
timer.Method.RemoveCache<Func<Object?, ValueTask>>(timer.Target.Target);
|
||||||
|
#endif
|
||||||
|
timer.Method.RemoveCache<Func<Object?, Task>>(timer.Target.Target);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private AutoResetEvent? _waitForTimer;
|
private AutoResetEvent? _waitForTimer;
|
||||||
@@ -244,7 +251,7 @@ public class TimerScheduler : IDisposable, ILogFeature
|
|||||||
// 必须在主线程设置状态,否则可能异步线程还没来得及设置开始状态,主线程又开始了新的一轮调度
|
// 必须在主线程设置状态,否则可能异步线程还没来得及设置开始状态,主线程又开始了新的一轮调度
|
||||||
timer.Calling = true;
|
timer.Calling = true;
|
||||||
if (timer.IsAsyncTask)
|
if (timer.IsAsyncTask)
|
||||||
ExecuteAsync(timer);
|
_ = ExecuteAsync(timer);
|
||||||
//Task.Factory.StartNew(ExecuteAsync, timer, CancellationToken.None, TaskCreationOptions.None, TaskScheduler.Default);
|
//Task.Factory.StartNew(ExecuteAsync, timer, CancellationToken.None, TaskCreationOptions.None, TaskScheduler.Default);
|
||||||
else if (!timer.Async)
|
else if (!timer.Async)
|
||||||
Execute(timer);
|
Execute(timer);
|
||||||
@@ -318,9 +325,10 @@ public class TimerScheduler : IDisposable, ILogFeature
|
|||||||
|
|
||||||
timer.hasSetNext = false;
|
timer.hasSetNext = false;
|
||||||
|
|
||||||
using var span = timer.Tracer?.NewSpan(timer.TracerName ?? $"timer:Execute", timer.Timers + "");
|
string tracerName = timer.TracerName ?? "timer:ExecuteAsync";
|
||||||
var sw = _stopwatchPool.Get();
|
string timerArg = timer.Timers.ToString();
|
||||||
sw.Restart();
|
using var span = timer.Tracer?.NewSpan(tracerName, timerArg);
|
||||||
|
var sw = ValueStopwatch.StartNew();
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
// 弱引用判断
|
// 弱引用判断
|
||||||
@@ -331,9 +339,12 @@ public class TimerScheduler : IDisposable, ILogFeature
|
|||||||
timer.Dispose();
|
timer.Dispose();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if (timer.TimerCallbackCachedDelegate == null)
|
||||||
var func = timer.Method.As<TimerCallback>(target);
|
{
|
||||||
func!(timer.State);
|
timer.TimerCallbackCachedDelegate = timer.Method.As<TimerCallback>(target);
|
||||||
|
}
|
||||||
|
//var func = timer.Method.As<TimerCallback>(target);
|
||||||
|
timer.TimerCallbackCachedDelegate!(timer.State);
|
||||||
}
|
}
|
||||||
catch (ThreadAbortException) { throw; }
|
catch (ThreadAbortException) { throw; }
|
||||||
catch (ThreadInterruptedException) { throw; }
|
catch (ThreadInterruptedException) { throw; }
|
||||||
@@ -345,78 +356,89 @@ public class TimerScheduler : IDisposable, ILogFeature
|
|||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
sw.Stop();
|
var ms = sw.GetElapsedTime().TotalMilliseconds;
|
||||||
|
OnExecuted(timer, (Int32)ms);
|
||||||
OnExecuted(timer, (Int32)sw.ElapsedMilliseconds);
|
|
||||||
|
|
||||||
_stopwatchPool.Return(sw);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
private static ObjectPool<Stopwatch> _stopwatchPool { get; } = new ObjectPool<Stopwatch>();
|
|
||||||
|
|
||||||
/// <summary>处理每一个定时器</summary>
|
/// <summary>处理每一个定时器</summary>
|
||||||
/// <param name="state"></param>
|
/// <param name="state"></param>
|
||||||
private async void ExecuteAsync(Object? state)
|
private Task ExecuteAsync(Object? state)
|
||||||
{
|
{
|
||||||
if (state is not TimerX timer) return;
|
return ExecuteAsync(this, state);
|
||||||
|
static async PooledTask ExecuteAsync(TimerScheduler @this, Object? state)
|
||||||
//TimerX.Current = timer;
|
|
||||||
|
|
||||||
// 控制日志显示
|
|
||||||
//WriteLogEventArgs.CurrentThreadName = Name == "Default" ? "T" : Name;
|
|
||||||
|
|
||||||
timer.hasSetNext = false;
|
|
||||||
|
|
||||||
using var span = timer.Tracer?.NewSpan(timer.TracerName ?? $"timer:ExecuteAsync", timer.Timers + "");
|
|
||||||
var sw = _stopwatchPool.Get();
|
|
||||||
sw.Restart();
|
|
||||||
try
|
|
||||||
{
|
{
|
||||||
// 弱引用判断
|
if (state is not TimerX timer) return;
|
||||||
var target = timer.Target.Target;
|
|
||||||
if (target == null && !timer.Method.IsStatic)
|
//TimerX.Current = timer;
|
||||||
|
|
||||||
|
// 控制日志显示
|
||||||
|
//WriteLogEventArgs.CurrentThreadName = Name == "Default" ? "T" : Name;
|
||||||
|
|
||||||
|
timer.hasSetNext = false;
|
||||||
|
|
||||||
|
string tracerName = timer.TracerName ?? "timer:ExecuteAsync";
|
||||||
|
string timerArg = timer.Timers.ToString();
|
||||||
|
using var span = timer.Tracer?.NewSpan(tracerName, timerArg);
|
||||||
|
var sw = ValueStopwatch.StartNew();
|
||||||
|
try
|
||||||
{
|
{
|
||||||
Remove(timer, "委托已不存在(GC回收委托所在对象)");
|
// 弱引用判断
|
||||||
timer.Dispose();
|
var target = timer.Target.Target;
|
||||||
return;
|
if (target == null && !timer.Method.IsStatic)
|
||||||
}
|
{
|
||||||
|
@this.Remove(timer, "委托已不存在(GC回收委托所在对象)");
|
||||||
|
timer.Dispose();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#if NET6_0_OR_GREATER
|
#if NET6_0_OR_GREATER
|
||||||
if (timer.IsValueTask)
|
if (timer.IsValueTask)
|
||||||
{
|
{
|
||||||
var func = timer.Method.As<Func<Object?, ValueTask>>(target);
|
if (timer.ValueTaskCachedDelegate == null)
|
||||||
var task = func!(timer.State);
|
{
|
||||||
if (!task.IsCompleted)
|
timer.ValueTaskCachedDelegate = timer.Method.As<Func<Object?, ValueTask>>(target);
|
||||||
await task.ConfigureAwait(false);
|
}
|
||||||
}
|
|
||||||
else
|
//var func = timer.Method.As<Func<Object?, ValueTask>>(target);
|
||||||
|
var task = timer.ValueTaskCachedDelegate!(timer.State);
|
||||||
|
if (!task.IsCompleted)
|
||||||
|
await task.ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
else
|
||||||
#endif
|
#endif
|
||||||
{
|
{
|
||||||
var func = timer.Method.As<Func<Object?, Task>>(target);
|
if (timer.TaskCachedDelegate == null)
|
||||||
var task = func!(timer.State);
|
{
|
||||||
if (!task.IsCompleted)
|
timer.TaskCachedDelegate = timer.Method.As<Func<Object?, Task>>(target);
|
||||||
await task.ConfigureAwait(false);
|
}
|
||||||
|
|
||||||
|
//var func = timer.Method.As<Func<Object?, Task>>(target);
|
||||||
|
var task = timer.TaskCachedDelegate!(timer.State);
|
||||||
|
if (!task.IsCompleted)
|
||||||
|
await task.ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
catch (ThreadAbortException) { throw; }
|
||||||
|
catch (ThreadInterruptedException) { throw; }
|
||||||
|
// 如果用户代码没有拦截错误,则这里拦截,避免出错了都不知道怎么回事
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
span?.SetError(ex, null);
|
||||||
|
XTrace.WriteException(ex);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
var ms = sw.GetElapsedTime().TotalMilliseconds;
|
||||||
|
|
||||||
}
|
@this.OnExecuted(timer, (Int32)ms);
|
||||||
catch (ThreadAbortException) { throw; }
|
|
||||||
catch (ThreadInterruptedException) { throw; }
|
|
||||||
// 如果用户代码没有拦截错误,则这里拦截,避免出错了都不知道怎么回事
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
span?.SetError(ex, null);
|
|
||||||
XTrace.WriteException(ex);
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
sw.Stop();
|
|
||||||
|
|
||||||
OnExecuted(timer, (Int32)sw.ElapsedMilliseconds);
|
}
|
||||||
|
|
||||||
_stopwatchPool.Return(sw);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnExecuted(TimerX timer, Int32 ms)
|
private void OnExecuted(TimerX timer, Int32 ms)
|
||||||
{
|
{
|
||||||
timer.Cost = timer.Cost == 0 ? ms : (timer.Cost + ms) / 2;
|
timer.Cost = timer.Cost == 0 ? ms : (timer.Cost + ms) / 2;
|
||||||
|
|||||||
@@ -542,6 +542,12 @@ public class TimerX : ITimer, ITimerx, IDisposable
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Func<object?, Task>? TaskCachedDelegate { get; internal set; }
|
||||||
|
#if NET6_0_OR_GREATER
|
||||||
|
public Func<object?, ValueTask>? ValueTaskCachedDelegate { get; internal set; }
|
||||||
|
#endif
|
||||||
|
public TimerCallback? TimerCallbackCachedDelegate { get; internal set; }
|
||||||
|
|
||||||
private static void CopyNow(Object? state) => _Now = TimerScheduler.Default.GetNow();
|
private static void CopyNow(Object? state) => _Now = TimerScheduler.Default.GetNow();
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
using System.Data;
|
using System.Data;
|
||||||
using System.Data.Common;
|
using System.Data.Common;
|
||||||
using System.Numerics;
|
|
||||||
|
|
||||||
using ThingsGateway.NewLife.Reflection;
|
using ThingsGateway.NewLife.Reflection;
|
||||||
namespace ThingsGateway.SqlSugar
|
namespace ThingsGateway.SqlSugar
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
<Project>
|
<Project>
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<PluginVersion>10.11.109</PluginVersion>
|
<PluginVersion>10.11.118</PluginVersion>
|
||||||
<ProPluginVersion>10.11.109</ProPluginVersion>
|
<ProPluginVersion>10.11.118</ProPluginVersion>
|
||||||
<DefaultVersion>10.11.109</DefaultVersion>
|
<DefaultVersion>10.11.118</DefaultVersion>
|
||||||
<AuthenticationVersion>10.11.6</AuthenticationVersion>
|
<AuthenticationVersion>10.11.6</AuthenticationVersion>
|
||||||
<SourceGeneratorVersion>10.11.6</SourceGeneratorVersion>
|
<SourceGeneratorVersion>10.11.6</SourceGeneratorVersion>
|
||||||
<NET8Version>8.0.21</NET8Version>
|
<NET8Version>8.0.21</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.120</TSVersion>
|
<TSVersion>4.0.0-beta.140</TSVersion>
|
||||||
|
|
||||||
|
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|||||||
@@ -11,6 +11,8 @@
|
|||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
using Newtonsoft.Json.Linq;
|
using Newtonsoft.Json.Linq;
|
||||||
|
|
||||||
|
using PooledAwait;
|
||||||
|
|
||||||
using System.Linq.Expressions;
|
using System.Linq.Expressions;
|
||||||
|
|
||||||
using ThingsGateway.Gateway.Application.Extensions;
|
using ThingsGateway.Gateway.Application.Extensions;
|
||||||
@@ -139,18 +141,27 @@ public abstract class VariableObject
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// <see cref="VariableRuntimeAttribute"/>特性连读,反射赋值到继承类中的属性
|
/// <see cref="VariableRuntimeAttribute"/>特性连读,反射赋值到继承类中的属性
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public virtual async ValueTask<OperResult> MultiReadAsync(CancellationToken cancellationToken = default)
|
public virtual ValueTask<OperResult> MultiReadAsync(CancellationToken cancellationToken = default)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
GetVariableSources();
|
GetVariableSources();
|
||||||
//连读
|
//连读
|
||||||
foreach (var item in DeviceVariableSourceReads)
|
return MultiReadAsync(this, cancellationToken);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
return EasyValueTask.FromResult(new OperResult(ex));
|
||||||
|
}
|
||||||
|
|
||||||
|
static async PooledValueTask<OperResult> MultiReadAsync(VariableObject @this, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
foreach (var item in @this.DeviceVariableSourceReads)
|
||||||
{
|
{
|
||||||
var result = await Device.ReadAsync(item.RegisterAddress, item.Length, cancellationToken).ConfigureAwait(false);
|
var result = await @this.Device.ReadAsync(item.RegisterAddress, item.Length, cancellationToken).ConfigureAwait(false);
|
||||||
if (result.IsSuccess)
|
if (result.IsSuccess)
|
||||||
{
|
{
|
||||||
var result1 = item.VariableRuntimes.PraseStructContent(Device, result.Content.Span, exWhenAny: true);
|
var result1 = item.VariableRuntimes.PraseStructContent(@this.Device, result.Content.Span, exWhenAny: true);
|
||||||
if (!result1.IsSuccess)
|
if (!result1.IsSuccess)
|
||||||
{
|
{
|
||||||
item.LastErrorMessage = result1.ErrorMessage;
|
item.LastErrorMessage = result1.ErrorMessage;
|
||||||
@@ -168,13 +179,9 @@ public abstract class VariableObject
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
SetValue();
|
@this.SetValue();
|
||||||
return OperResult.Success;
|
return OperResult.Success;
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
return new OperResult(ex);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -205,30 +212,31 @@ public abstract class VariableObject
|
|||||||
/// <param name="propertyName">属性名称,必须使用<see cref="VariableRuntimeAttribute"/>特性</param>
|
/// <param name="propertyName">属性名称,必须使用<see cref="VariableRuntimeAttribute"/>特性</param>
|
||||||
/// <param name="value">写入值</param>
|
/// <param name="value">写入值</param>
|
||||||
/// <param name="cancellationToken">取消令箭</param>
|
/// <param name="cancellationToken">取消令箭</param>
|
||||||
public virtual async ValueTask<OperResult> WriteValueAsync(string propertyName, object value, CancellationToken cancellationToken = default)
|
public virtual ValueTask<OperResult> WriteValueAsync(string propertyName, object value, CancellationToken cancellationToken = default)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
GetVariableSources();
|
GetVariableSources();
|
||||||
if (string.IsNullOrEmpty(propertyName))
|
if (string.IsNullOrEmpty(propertyName))
|
||||||
{
|
{
|
||||||
return new OperResult($"PropertyName cannot be null or empty.");
|
return EasyValueTask.FromResult(new OperResult($"PropertyName cannot be null or empty."));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!VariableRuntimePropertyDict.TryGetValue(propertyName, out var variableRuntimeProperty))
|
if (!VariableRuntimePropertyDict.TryGetValue(propertyName, out var variableRuntimeProperty))
|
||||||
{
|
{
|
||||||
return new OperResult($"This attribute is not recognized and may not have been identified using the {typeof(VariableRuntimeAttribute)} attribute");
|
return EasyValueTask.FromResult(new OperResult($"This attribute is not recognized and may not have been identified using the {typeof(VariableRuntimeAttribute)} attribute"));
|
||||||
}
|
}
|
||||||
|
|
||||||
JToken jToken = GetExpressionsValue(value, variableRuntimeProperty);
|
JToken jToken = GetExpressionsValue(value, variableRuntimeProperty);
|
||||||
|
|
||||||
var result = await Device.WriteJTokenAsync(variableRuntimeProperty.VariableClass.RegisterAddress, jToken, variableRuntimeProperty.VariableClass.DataType, cancellationToken).ConfigureAwait(false);
|
return Device.WriteJTokenAsync(variableRuntimeProperty.VariableClass.RegisterAddress, jToken, variableRuntimeProperty.VariableClass.DataType, cancellationToken);
|
||||||
return result;
|
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
return new OperResult(ex);
|
return EasyValueTask.FromResult(new OperResult(ex));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|||||||
@@ -8,6 +8,8 @@
|
|||||||
// QQ群:605534569
|
// QQ群:605534569
|
||||||
//------------------------------------------------------------------------------
|
//------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
using PooledAwait;
|
||||||
|
|
||||||
using System.Runtime.CompilerServices;
|
using System.Runtime.CompilerServices;
|
||||||
|
|
||||||
using TouchSocket.Resources;
|
using TouchSocket.Resources;
|
||||||
@@ -138,82 +140,86 @@ public class DDPTcpSessionClientChannel : TcpSessionClientChannel
|
|||||||
|
|
||||||
private DeviceSingleStreamDataHandleAdapter<DDPTcpMessage> DDPAdapter = new();
|
private DeviceSingleStreamDataHandleAdapter<DDPTcpMessage> DDPAdapter = new();
|
||||||
|
|
||||||
protected override async ValueTask<bool> OnTcpReceiving(IBytesReader byteBlock)
|
protected override ValueTask<bool> OnTcpReceiving(IBytesReader byteBlock)
|
||||||
{
|
{
|
||||||
|
|
||||||
if (DDPAdapter.TryParseRequest(ref byteBlock, out var message))
|
if (DDPAdapter.TryParseRequest(ref byteBlock, out var message))
|
||||||
{
|
{
|
||||||
return true;
|
return EasyValueTask.FromResult(true);
|
||||||
}
|
}
|
||||||
|
return OnTcpReceiving(this, message);
|
||||||
|
|
||||||
if (message != null)
|
static async PooledValueTask<bool> OnTcpReceiving(DDPTcpSessionClientChannel @this, DDPTcpMessage message)
|
||||||
{
|
{
|
||||||
if (message.IsSuccess)
|
if (message != null)
|
||||||
{
|
{
|
||||||
var id = $"ID={message.Id}";
|
if (message.IsSuccess)
|
||||||
if (message.Type == 0x09)
|
|
||||||
{
|
{
|
||||||
var reader = new ClassBytesReader(message.Content);
|
var id = $"ID={message.Id}";
|
||||||
|
if (message.Type == 0x09)
|
||||||
if (this.DataHandlingAdapter == null)
|
|
||||||
{
|
{
|
||||||
await this.OnTcpReceived(new ReceivedDataEventArgs(message.Content, default)).ConfigureAwait(false);
|
var reader = new ClassBytesReader(message.Content);
|
||||||
|
|
||||||
|
if (@this.DataHandlingAdapter == null)
|
||||||
|
{
|
||||||
|
await @this.OnTcpReceived(new ReceivedDataEventArgs(message.Content, default)).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
await @this.DataHandlingAdapter.ReceivedInputAsync(reader).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
await this.DataHandlingAdapter.ReceivedInputAsync(reader).ConfigureAwait(false);
|
if (message.Type == 0x01)
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
if (message.Type == 0x01)
|
|
||||||
{
|
|
||||||
bool log = false;
|
|
||||||
if (id != Id) log = true;
|
|
||||||
|
|
||||||
//注册ID
|
|
||||||
if (Service is ITcpServiceChannel tcpService && tcpService.TryGetClient(id, out var oldClient) && oldClient != this)
|
|
||||||
{
|
{
|
||||||
Logger?.Debug($"Old socket connections with the same ID {id} will be closed");
|
bool log = false;
|
||||||
try
|
if (id != @this.Id) log = true;
|
||||||
{
|
|
||||||
//await oldClient.ShutdownAsync(System.Net.Sockets.SocketShutdown.Both).ConfigureAwait(false);
|
//注册ID
|
||||||
await oldClient.CloseAsync().ConfigureAwait(false);
|
if (@this.Service is ITcpServiceChannel tcpService && tcpService.TryGetClient(id, out var oldClient) && oldClient != @this)
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
}
|
|
||||||
try
|
|
||||||
{
|
|
||||||
oldClient.Dispose();
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
{
|
||||||
|
@this.Logger?.Debug($"Old socket connections with the same ID {id} will be closed");
|
||||||
|
try
|
||||||
|
{
|
||||||
|
//await oldClient.ShutdownAsync(System.Net.Sockets.SocketShutdown.Both).ConfigureAwait(false);
|
||||||
|
await oldClient.CloseAsync().ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
}
|
||||||
|
try
|
||||||
|
{
|
||||||
|
oldClient.Dispose();
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
await @this.ResetIdAsync(id, @this.ClosedToken).ConfigureAwait(false);
|
||||||
|
|
||||||
|
//发送成功
|
||||||
|
await @this.ProtectedSendAsync(new DDPSend(ReadOnlyMemory<byte>.Empty, id, true, 0x81), @this.ClosedToken).ConfigureAwait(false);
|
||||||
|
if (log)
|
||||||
|
@this.Logger?.Info(string.Format(AppResource.DtuConnected, @this.Id));
|
||||||
|
}
|
||||||
|
else if (message.Type == 0x02)
|
||||||
|
{
|
||||||
|
await @this.ProtectedSendAsync(new DDPSend(ReadOnlyMemory<byte>.Empty, @this.Id, true, 0x82), @this.ClosedToken).ConfigureAwait(false);
|
||||||
|
@this.Logger?.Info(string.Format(AppResource.DtuDisconnecting, @this.Id));
|
||||||
|
await Task.Delay(100).ConfigureAwait(false);
|
||||||
|
await @this.CloseAsync().ConfigureAwait(false);
|
||||||
|
@this.SafeDispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
await ResetIdAsync(id, ClosedToken).ConfigureAwait(false);
|
|
||||||
|
|
||||||
//发送成功
|
|
||||||
await base.ProtectedSendAsync(new DDPSend(ReadOnlyMemory<byte>.Empty, id, true, 0x81), ClosedToken).ConfigureAwait(false);
|
|
||||||
if (log)
|
|
||||||
Logger?.Info(string.Format(AppResource.DtuConnected, Id));
|
|
||||||
}
|
|
||||||
else if (message.Type == 0x02)
|
|
||||||
{
|
|
||||||
await base.ProtectedSendAsync(new DDPSend(ReadOnlyMemory<byte>.Empty, Id, true, 0x82), ClosedToken).ConfigureAwait(false);
|
|
||||||
Logger?.Info(string.Format(AppResource.DtuDisconnecting, Id));
|
|
||||||
await Task.Delay(100).ConfigureAwait(false);
|
|
||||||
await this.CloseAsync().ConfigureAwait(false);
|
|
||||||
this.SafeDispose();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#region Throw
|
#region Throw
|
||||||
|
|||||||
@@ -8,6 +8,8 @@
|
|||||||
// QQ群:605534569
|
// QQ群:605534569
|
||||||
//------------------------------------------------------------------------------
|
//------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
using PooledAwait;
|
||||||
|
|
||||||
using System.Collections.Concurrent;
|
using System.Collections.Concurrent;
|
||||||
using System.Net;
|
using System.Net;
|
||||||
using System.Runtime.CompilerServices;
|
using System.Runtime.CompilerServices;
|
||||||
@@ -80,73 +82,78 @@ public class DDPUdpSessionChannel : UdpSessionChannel, IClientChannel, IDtuUdpSe
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
protected override async ValueTask<bool> OnUdpReceiving(UdpReceiveingEventArgs e)
|
protected override ValueTask<bool> OnUdpReceiving(UdpReceiveingEventArgs e)
|
||||||
{
|
{
|
||||||
var byteBlock = e.Memory;
|
var byteBlock = e.Memory;
|
||||||
var endPoint = e.EndPoint;
|
var endPoint = e.EndPoint;
|
||||||
|
|
||||||
if (!DDPAdapter.TryParseRequest(endPoint, byteBlock, out var message))
|
if (!DDPAdapter.TryParseRequest(endPoint, byteBlock, out var message))
|
||||||
return true;
|
return EasyValueTask.FromResult(true);
|
||||||
|
|
||||||
if (message != null)
|
return OnUdpReceiving(this, endPoint, message);
|
||||||
|
|
||||||
|
static async PooledValueTask<bool> OnUdpReceiving(DDPUdpSessionChannel @this, EndPoint endPoint, DDPUdpMessage message)
|
||||||
{
|
{
|
||||||
if (message.IsSuccess)
|
if (message != null)
|
||||||
{
|
{
|
||||||
var id = $"ID={message.Id}";
|
if (message.IsSuccess)
|
||||||
if (message.Type == 0x09)
|
|
||||||
{
|
{
|
||||||
if (this.DataHandlingAdapter == null)
|
var id = $"ID={message.Id}";
|
||||||
|
if (message.Type == 0x09)
|
||||||
{
|
{
|
||||||
await this.OnUdpReceived(new UdpReceivedDataEventArgs(endPoint, message.Content, default)).ConfigureAwait(false);
|
if (@this.DataHandlingAdapter == null)
|
||||||
|
{
|
||||||
|
await @this.OnUdpReceived(new UdpReceivedDataEventArgs(endPoint, message.Content, default)).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
await @this.DataHandlingAdapter.ReceivedInputAsync(endPoint, message.Content).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
await this.DataHandlingAdapter.ReceivedInputAsync(endPoint, message.Content).ConfigureAwait(false);
|
if (message.Type == 0x01)
|
||||||
}
|
{
|
||||||
|
bool log = false;
|
||||||
|
|
||||||
return true;
|
//注册ID
|
||||||
}
|
if (!@this.IdDict.TryAdd(endPoint, id))
|
||||||
else
|
{
|
||||||
{
|
@this.IdDict[endPoint] = id;
|
||||||
if (message.Type == 0x01)
|
}
|
||||||
{
|
else
|
||||||
bool log = false;
|
{
|
||||||
|
log = true;
|
||||||
|
}
|
||||||
|
if (!@this.EndPointDcit.TryAdd(id, endPoint))
|
||||||
|
{
|
||||||
|
@this.EndPointDcit[id] = endPoint;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
log = true;
|
||||||
|
}
|
||||||
|
|
||||||
//注册ID
|
//发送成功
|
||||||
if (!IdDict.TryAdd(endPoint, id))
|
await @this.DDPAdapter.SendInputAsync(endPoint, new DDPSend(ReadOnlyMemory<byte>.Empty, id, false, 0x81), @this.ClosedToken).ConfigureAwait(false);
|
||||||
{
|
if (log)
|
||||||
IdDict[endPoint] = id;
|
@this.Logger?.Info(string.Format(AppResource.DtuConnected, id));
|
||||||
}
|
}
|
||||||
else
|
else if (message.Type == 0x02)
|
||||||
{
|
{
|
||||||
log = true;
|
await @this.DDPAdapter.SendInputAsync(endPoint, new DDPSend(ReadOnlyMemory<byte>.Empty, id, false, 0x82), @this.ClosedToken).ConfigureAwait(false);
|
||||||
|
@this.Logger?.Info(string.Format(AppResource.DtuDisconnecting, id));
|
||||||
|
await Task.Delay(100).ConfigureAwait(false);
|
||||||
|
@this.IdDict.TryRemove(endPoint, out _);
|
||||||
|
@this.EndPointDcit.TryRemove(id, out _);
|
||||||
}
|
}
|
||||||
if (!EndPointDcit.TryAdd(id, endPoint))
|
|
||||||
{
|
|
||||||
EndPointDcit[id] = endPoint;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
log = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
//发送成功
|
|
||||||
await DDPAdapter.SendInputAsync(endPoint, new DDPSend(ReadOnlyMemory<byte>.Empty, id, false, 0x81), ClosedToken).ConfigureAwait(false);
|
|
||||||
if (log)
|
|
||||||
Logger?.Info(string.Format(AppResource.DtuConnected, id));
|
|
||||||
}
|
|
||||||
else if (message.Type == 0x02)
|
|
||||||
{
|
|
||||||
await DDPAdapter.SendInputAsync(endPoint, new DDPSend(ReadOnlyMemory<byte>.Empty, id, false, 0x82), ClosedToken).ConfigureAwait(false);
|
|
||||||
Logger?.Info(string.Format(AppResource.DtuDisconnecting, id));
|
|
||||||
await Task.Delay(100).ConfigureAwait(false);
|
|
||||||
IdDict.TryRemove(endPoint, out _);
|
|
||||||
EndPointDcit.TryRemove(id, out _);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#region Throw
|
#region Throw
|
||||||
|
|||||||
@@ -8,6 +8,8 @@
|
|||||||
// QQ群:605534569
|
// QQ群:605534569
|
||||||
//------------------------------------------------------------------------------
|
//------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
using PooledAwait;
|
||||||
|
|
||||||
using ThingsGateway.Foundation.Extension.String;
|
using ThingsGateway.Foundation.Extension.String;
|
||||||
|
|
||||||
using TouchSocket.SerialPorts;
|
using TouchSocket.SerialPorts;
|
||||||
@@ -26,22 +28,27 @@ public static class ChannelOptionsExtensions
|
|||||||
/// <param name="e">接收数据</param>
|
/// <param name="e">接收数据</param>
|
||||||
/// <param name="funcs">事件</param>
|
/// <param name="funcs">事件</param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
internal static async Task OnChannelReceivedEvent(this IClientChannel clientChannel, ReceivedDataEventArgs e, ChannelReceivedEventHandler funcs)
|
internal static ValueTask OnChannelReceivedEvent(this IClientChannel clientChannel, ReceivedDataEventArgs e, ChannelReceivedEventHandler funcs)
|
||||||
{
|
{
|
||||||
clientChannel.ThrowIfNull(nameof(IClientChannel));
|
clientChannel.ThrowIfNull(nameof(IClientChannel));
|
||||||
e.ThrowIfNull(nameof(ReceivedDataEventArgs));
|
e.ThrowIfNull(nameof(ReceivedDataEventArgs));
|
||||||
funcs.ThrowIfNull(nameof(ChannelReceivedEventHandler));
|
funcs.ThrowIfNull(nameof(ChannelReceivedEventHandler));
|
||||||
|
|
||||||
if (funcs.Count > 0)
|
return OnChannelReceivedEvent(clientChannel, e, funcs);
|
||||||
|
|
||||||
|
static async PooledValueTask OnChannelReceivedEvent(IClientChannel clientChannel, ReceivedDataEventArgs e, ChannelReceivedEventHandler funcs)
|
||||||
{
|
{
|
||||||
for (int i = 0; i < funcs.Count; i++)
|
if (funcs.Count > 0)
|
||||||
{
|
{
|
||||||
var func = funcs[i];
|
for (int i = 0; i < funcs.Count; i++)
|
||||||
if (func == null) continue;
|
|
||||||
await func.Invoke(clientChannel, e, i == funcs.Count - 1).ConfigureAwait(false);
|
|
||||||
if (e.Handled)
|
|
||||||
{
|
{
|
||||||
break;
|
var func = funcs[i];
|
||||||
|
if (func == null) continue;
|
||||||
|
await func.Invoke(clientChannel, e, i == funcs.Count - 1).ConfigureAwait(false);
|
||||||
|
if (e.Handled)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -53,7 +60,7 @@ public static class ChannelOptionsExtensions
|
|||||||
/// <param name="clientChannel">通道</param>
|
/// <param name="clientChannel">通道</param>
|
||||||
/// <param name="funcs">事件</param>
|
/// <param name="funcs">事件</param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
internal static async Task OnChannelEvent(this IClientChannel clientChannel, ChannelEventHandler funcs)
|
internal static async ValueTask OnChannelEvent(this IClientChannel clientChannel, ChannelEventHandler funcs)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -65,7 +65,7 @@ public interface IChannel : ISetupConfigObject, IDisposable, IClosableClient, IC
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// 接收事件回调类
|
/// 接收事件回调类
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class ChannelReceivedEventHandler : List<Func<IClientChannel, ReceivedDataEventArgs, bool, Task>>
|
public class ChannelReceivedEventHandler : List<Func<IClientChannel, ReceivedDataEventArgs, bool, ValueTask>>
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -131,10 +131,10 @@ public class OtherChannel : SetupConfigObject, IClientChannel
|
|||||||
m_dataHandlingAdapter = adapter;
|
m_dataHandlingAdapter = adapter;
|
||||||
}
|
}
|
||||||
|
|
||||||
private Task PrivateHandleReceivedData(ReadOnlyMemory<byte> byteBlock, IRequestInfo requestInfo)
|
private async Task PrivateHandleReceivedData(ReadOnlyMemory<byte> byteBlock, IRequestInfo requestInfo)
|
||||||
{
|
{
|
||||||
LastReceivedTime = DateTime.Now;
|
LastReceivedTime = DateTime.Now;
|
||||||
return this.OnChannelReceivedEvent(new ReceivedDataEventArgs(byteBlock, requestInfo), ChannelReceived);
|
await this.OnChannelReceivedEvent(new ReceivedDataEventArgs(byteBlock, requestInfo), ChannelReceived).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|||||||
@@ -8,6 +8,8 @@
|
|||||||
// QQ群:605534569
|
// QQ群:605534569
|
||||||
//------------------------------------------------------------------------------
|
//------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
using PooledAwait;
|
||||||
|
|
||||||
using System.Text;
|
using System.Text;
|
||||||
|
|
||||||
using ThingsGateway.Foundation.Extension.String;
|
using ThingsGateway.Foundation.Extension.String;
|
||||||
@@ -59,64 +61,71 @@ public class DtuPlugin : PluginBase, ITcpReceivingPlugin
|
|||||||
public bool DtuIdHex { get; set; }
|
public bool DtuIdHex { get; set; }
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public async Task OnTcpReceiving(ITcpSession client, BytesReaderEventArgs e)
|
public Task OnTcpReceiving(ITcpSession client, BytesReaderEventArgs e)
|
||||||
{
|
{
|
||||||
var len = HeartbeatByte.Length;
|
return OnTcpReceiving(this, client, e);
|
||||||
if (client is TcpSessionClientChannel socket && socket.Service is ITcpServiceChannel tcpServiceChannel)
|
|
||||||
|
|
||||||
|
static async PooledTask OnTcpReceiving(DtuPlugin @this, ITcpSession client, BytesReaderEventArgs e)
|
||||||
{
|
{
|
||||||
if (!socket.Id.StartsWith("ID="))
|
var len = @this.HeartbeatByte.Length;
|
||||||
|
if (client is TcpSessionClientChannel socket && socket.Service is ITcpServiceChannel tcpServiceChannel)
|
||||||
{
|
{
|
||||||
var id = DtuIdHex ? $"ID={e.Reader.ToHexString()}" : $"ID={e.Reader.TotalSequence.ToString(Encoding.UTF8)}";
|
if (!socket.Id.StartsWith("ID="))
|
||||||
if (tcpServiceChannel.TryGetClient(id, out var oldClient))
|
{
|
||||||
|
var id = @this.DtuIdHex ? $"ID={e.Reader.ToHexString()}" : $"ID={e.Reader.TotalSequence.ToString(Encoding.UTF8)}";
|
||||||
|
if (tcpServiceChannel.TryGetClient(id, out var oldClient))
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await oldClient.CloseAsync().ConfigureAwait(false);
|
||||||
|
oldClient.Dispose();
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
await socket.ResetIdAsync(id, client.ClosedToken).ConfigureAwait(false);
|
||||||
|
client.Logger?.Info(string.Format(AppResource.DtuConnected, id));
|
||||||
|
e.Reader.Advance((int)e.Reader.BytesRemaining);
|
||||||
|
e.Handled = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!socket.Service.ClientExists(socket.Id))
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
await oldClient.CloseAsync().ConfigureAwait(false);
|
await socket.CloseAsync().ConfigureAwait(false);
|
||||||
oldClient.Dispose();
|
socket.Dispose();
|
||||||
}
|
}
|
||||||
catch
|
catch
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
}
|
|
||||||
await socket.ResetIdAsync(id, client.ClosedToken).ConfigureAwait(false);
|
|
||||||
client.Logger?.Info(string.Format(AppResource.DtuConnected, id));
|
|
||||||
e.Reader.Advance((int)e.Reader.BytesRemaining);
|
|
||||||
e.Handled = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!socket.Service.ClientExists(socket.Id))
|
await e.InvokeNext().ConfigureAwait(false);//如果本插件无法处理当前数据,请将数据转至下一个插件。
|
||||||
{
|
return;
|
||||||
try
|
|
||||||
{
|
|
||||||
await socket.CloseAsync().ConfigureAwait(false);
|
|
||||||
socket.Dispose();
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
}
|
}
|
||||||
|
|
||||||
await e.InvokeNext().ConfigureAwait(false);//如果本插件无法处理当前数据,请将数据转至下一个插件。
|
if (len > 0)
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (len > 0)
|
|
||||||
{
|
|
||||||
if (HeartbeatByte.Span.SequenceEqual(e.Reader.TotalSequence.Slice(0, (int)Math.Min(len, e.Reader.BytesRemaining + e.Reader.BytesRead)).First.Span))
|
|
||||||
{
|
{
|
||||||
if (DateTimeOffset.Now - socket.LastSentTime < TimeSpan.FromMilliseconds(200))
|
if (@this.HeartbeatByte.Span.SequenceEqual(e.Reader.TotalSequence.Slice(0, (int)Math.Min(len, e.Reader.BytesRemaining + e.Reader.BytesRead)).First.Span))
|
||||||
{
|
{
|
||||||
await Task.Delay(200, client.ClosedToken).ConfigureAwait(false);
|
if (DateTimeOffset.Now - socket.LastSentTime < TimeSpan.FromMilliseconds(200))
|
||||||
|
{
|
||||||
|
await Task.Delay(200, client.ClosedToken).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
//回应心跳包
|
||||||
|
await socket.SendAsync(@this.HeartbeatByte, socket.ClosedToken).ConfigureAwait(false);
|
||||||
|
e.Reader.Advance((int)Math.Min(len, e.Reader.BytesRemaining));
|
||||||
|
e.Handled = true;
|
||||||
|
if (socket.Logger?.LogLevel <= LogLevel.Trace)
|
||||||
|
socket.Logger?.Trace($"{socket}- Heartbeat");
|
||||||
}
|
}
|
||||||
//回应心跳包
|
|
||||||
await socket.SendAsync(HeartbeatByte, socket.ClosedToken).ConfigureAwait(false);
|
|
||||||
e.Reader.Advance((int)Math.Min(len, e.Reader.BytesRemaining));
|
|
||||||
e.Handled = true;
|
|
||||||
if (socket.Logger?.LogLevel <= LogLevel.Trace)
|
|
||||||
socket.Logger?.Trace($"{socket}- Heartbeat");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
await e.InvokeNext().ConfigureAwait(false);//如果本插件无法处理当前数据,请将数据转至下一个插件。
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
await e.InvokeNext().ConfigureAwait(false);//如果本插件无法处理当前数据,请将数据转至下一个插件。
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -167,14 +167,14 @@ internal sealed class HeartbeatAndReceivePlugin : PluginBase, ITcpConnectedPlugi
|
|||||||
await e.InvokeNext().ConfigureAwait(false);
|
await e.InvokeNext().ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task OnTcpReceiving(ITcpSession client, BytesReaderEventArgs e)
|
public Task OnTcpReceiving(ITcpSession client, BytesReaderEventArgs e)
|
||||||
{
|
{
|
||||||
if (client is ITcpSessionClient)
|
if (client is ITcpSessionClient)
|
||||||
{
|
{
|
||||||
return;//此处可判断,如果为服务器,则不用使用心跳。
|
return Task.CompletedTask;//此处可判断,如果为服务器,则不用使用心跳。
|
||||||
}
|
}
|
||||||
|
|
||||||
if (DtuId.IsNullOrWhiteSpace()) return;
|
if (DtuId.IsNullOrWhiteSpace()) return Task.CompletedTask;
|
||||||
|
|
||||||
if (client is ITcpClient tcpClient)
|
if (client is ITcpClient tcpClient)
|
||||||
{
|
{
|
||||||
@@ -187,8 +187,9 @@ internal sealed class HeartbeatAndReceivePlugin : PluginBase, ITcpConnectedPlugi
|
|||||||
e.Handled = true;
|
e.Handled = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
await e.InvokeNext().ConfigureAwait(false);//如果本插件无法处理当前数据,请将数据转至下一个插件。
|
return e.InvokeNext();//如果本插件无法处理当前数据,请将数据转至下一个插件。
|
||||||
}
|
}
|
||||||
|
return Task.CompletedTask;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -34,25 +34,29 @@ public static class PluginUtil
|
|||||||
|
|
||||||
if (channelOptions.ChannelType == ChannelTypeEnum.TcpClient)
|
if (channelOptions.ChannelType == ChannelTypeEnum.TcpClient)
|
||||||
{
|
{
|
||||||
action += a => a.UseReconnection<IClientChannel>().SetActionForCheck((channel, failCount) =>
|
action += a => a.UseReconnection<IClientChannel>(a =>
|
||||||
{
|
{
|
||||||
if (channel.Online)
|
a.CheckAction = (channel, failCount) =>
|
||||||
{
|
{
|
||||||
return Task.FromResult(ConnectionCheckResult.Alive);
|
if (channel.Online)
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
if (failCount > 1)
|
|
||||||
{
|
{
|
||||||
return Task.FromResult(ConnectionCheckResult.Dead);
|
return Task.FromResult(ConnectionCheckResult.Alive);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (failCount > 1)
|
||||||
|
{
|
||||||
|
return Task.FromResult(ConnectionCheckResult.Dead);
|
||||||
|
}
|
||||||
|
return Task.FromResult(ConnectionCheckResult.Skip);
|
||||||
}
|
}
|
||||||
return Task.FromResult(ConnectionCheckResult.Skip);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
})
|
};
|
||||||
.SetPollingTick(TimeSpan.FromSeconds(5)
|
a.PollingInterval = TimeSpan.FromSeconds(5);
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return action;
|
return action;
|
||||||
|
|||||||
@@ -10,6 +10,8 @@
|
|||||||
|
|
||||||
using Newtonsoft.Json.Linq;
|
using Newtonsoft.Json.Linq;
|
||||||
|
|
||||||
|
using PooledAwait;
|
||||||
|
|
||||||
using System.Net;
|
using System.Net;
|
||||||
|
|
||||||
using ThingsGateway.Foundation.Extension.Generic;
|
using ThingsGateway.Foundation.Extension.Generic;
|
||||||
@@ -308,7 +310,7 @@ public abstract class DeviceBase : AsyncAndSyncDisposableObject, IDevice
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// 接收,非主动发送的情况,重写实现非主从并发通讯协议,如果通道存在其他设备并且不希望其他设备处理时,设置<see cref="TouchSocketEventArgs.Handled"/> 为true
|
/// 接收,非主动发送的情况,重写实现非主从并发通讯协议,如果通道存在其他设备并且不希望其他设备处理时,设置<see cref="TouchSocketEventArgs.Handled"/> 为true
|
||||||
/// </summary>
|
/// </summary>
|
||||||
protected virtual Task ChannelReceived(IClientChannel client, ReceivedDataEventArgs e, bool last)
|
protected virtual ValueTask ChannelReceived(IClientChannel client, ReceivedDataEventArgs e, bool last)
|
||||||
{
|
{
|
||||||
if (e.RequestInfo is MessageBase response)
|
if (e.RequestInfo is MessageBase response)
|
||||||
{
|
{
|
||||||
@@ -325,25 +327,29 @@ public abstract class DeviceBase : AsyncAndSyncDisposableObject, IDevice
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return EasyTask.CompletedTask;
|
return EasyValueTask.CompletedTask;
|
||||||
}
|
}
|
||||||
public bool AutoConnect { get; protected set; } = true;
|
public bool AutoConnect { get; protected set; } = true;
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
private async Task SendAsync(ISendMessage sendMessage, IClientChannel channel, CancellationToken token = default)
|
private Task SendAsync(ISendMessage sendMessage, IClientChannel channel, CancellationToken token = default)
|
||||||
{
|
{
|
||||||
|
return SendAsync(this, sendMessage, channel, token);
|
||||||
|
|
||||||
if (SendDelayTime != 0)
|
static async PooledTask SendAsync(DeviceBase @this, ISendMessage sendMessage, IClientChannel channel, CancellationToken token)
|
||||||
await Task.Delay(SendDelayTime, token).ConfigureAwait(false);
|
|
||||||
|
|
||||||
if (channel is IDtuUdpSessionChannel udpSession)
|
|
||||||
{
|
{
|
||||||
EndPoint? endPoint = GetUdpEndpoint();
|
if (@this.SendDelayTime != 0)
|
||||||
await udpSession.SendAsync(endPoint, sendMessage, token).ConfigureAwait(false);
|
await Task.Delay(@this.SendDelayTime, token).ConfigureAwait(false);
|
||||||
|
|
||||||
}
|
if (channel is IDtuUdpSessionChannel udpSession)
|
||||||
else
|
{
|
||||||
{
|
EndPoint? endPoint = @this.GetUdpEndpoint();
|
||||||
await channel.SendAsync(sendMessage, token).ConfigureAwait(false);
|
await udpSession.SendAsync(endPoint, sendMessage, token).ConfigureAwait(false);
|
||||||
|
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
await channel.SendAsync(sendMessage, token).ConfigureAwait(false);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -363,59 +369,69 @@ public abstract class DeviceBase : AsyncAndSyncDisposableObject, IDevice
|
|||||||
|
|
||||||
private WaitLock connectWaitLock = new(nameof(DeviceBase));
|
private WaitLock connectWaitLock = new(nameof(DeviceBase));
|
||||||
|
|
||||||
public async ValueTask ConnectAsync(CancellationToken token)
|
public ValueTask ConnectAsync(CancellationToken token)
|
||||||
{
|
{
|
||||||
if (AutoConnect && Channel != null && Channel?.Online != true)
|
return ConnectAsync(this, token);
|
||||||
|
|
||||||
|
static async PooledValueTask ConnectAsync(DeviceBase @this, CancellationToken token)
|
||||||
{
|
{
|
||||||
try
|
if (@this.AutoConnect && @this.Channel != null && @this.Channel?.Online != true)
|
||||||
{
|
{
|
||||||
await connectWaitLock.WaitAsync(token).ConfigureAwait(false);
|
try
|
||||||
if (AutoConnect && Channel != null && Channel?.Online != true)
|
|
||||||
{
|
{
|
||||||
if (Channel.PluginManager == null)
|
await @this.connectWaitLock.WaitAsync(token).ConfigureAwait(false);
|
||||||
await Channel.SetupAsync(Channel.Config.Clone()).ConfigureAwait(false);
|
if (@this.AutoConnect && @this.Channel != null && @this.Channel?.Online != true)
|
||||||
await Channel.CloseAsync().ConfigureAwait(false);
|
{
|
||||||
using var ctsTime = new CancellationTokenSource(Channel.ChannelOptions.ConnectTimeout);
|
if (@this.Channel.PluginManager == null)
|
||||||
using var cts = CancellationTokenSource.CreateLinkedTokenSource(ctsTime.Token, token);
|
await @this.Channel.SetupAsync(@this.Channel.Config.Clone()).ConfigureAwait(false);
|
||||||
await Channel.ConnectAsync(cts.Token).ConfigureAwait(false);
|
await @this.Channel.CloseAsync().ConfigureAwait(false);
|
||||||
|
using var ctsTime = new CancellationTokenSource(@this.Channel.ChannelOptions.ConnectTimeout);
|
||||||
|
using var cts = CancellationTokenSource.CreateLinkedTokenSource(ctsTime.Token, token);
|
||||||
|
await @this.Channel.ConnectAsync(cts.Token).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
@this.connectWaitLock.Release();
|
||||||
}
|
}
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
connectWaitLock.Release();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public virtual async ValueTask<OperResult> SendAsync(ISendMessage sendMessage, CancellationToken cancellationToken)
|
public virtual ValueTask<OperResult> SendAsync(ISendMessage sendMessage, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
try
|
return SendAsync(this, sendMessage, cancellationToken);
|
||||||
{
|
|
||||||
var channelResult = GetChannel();
|
|
||||||
if (!channelResult.IsSuccess) return new OperResult<byte[]>(channelResult);
|
|
||||||
WaitLock? waitLock = GetWaitLock(channelResult.Content);
|
|
||||||
|
|
||||||
|
static async PooledValueTask<OperResult> SendAsync(DeviceBase @this, ISendMessage sendMessage, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
await BeforeSendAsync(channelResult.Content, cancellationToken).ConfigureAwait(false);
|
var channelResult = @this.GetChannel();
|
||||||
|
if (!channelResult.IsSuccess) return new OperResult<byte[]>(channelResult);
|
||||||
|
WaitLock? waitLock = @this.GetWaitLock(channelResult.Content);
|
||||||
|
|
||||||
await waitLock.WaitAsync(cancellationToken).ConfigureAwait(false);
|
try
|
||||||
channelResult.Content.SetDataHandlingAdapterLogger(Logger);
|
{
|
||||||
|
await @this.BeforeSendAsync(channelResult.Content, cancellationToken).ConfigureAwait(false);
|
||||||
|
|
||||||
|
await waitLock.WaitAsync(cancellationToken).ConfigureAwait(false);
|
||||||
|
channelResult.Content.SetDataHandlingAdapterLogger(@this.Logger);
|
||||||
|
|
||||||
|
|
||||||
await SendAsync(sendMessage, channelResult.Content, cancellationToken).ConfigureAwait(false);
|
await @this.SendAsync(sendMessage, channelResult.Content, cancellationToken).ConfigureAwait(false);
|
||||||
return OperResult.Success;
|
return OperResult.Success;
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
waitLock.Release();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
finally
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
waitLock.Release();
|
return new(ex);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
return new(ex);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
@@ -496,16 +512,21 @@ public abstract class DeviceBase : AsyncAndSyncDisposableObject, IDevice
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public virtual async ValueTask<OperResult<ReadOnlyMemory<byte>>> SendThenReturnAsync(ISendMessage sendMessage, IClientChannel channel, CancellationToken cancellationToken = default)
|
public virtual ValueTask<OperResult<ReadOnlyMemory<byte>>> SendThenReturnAsync(ISendMessage sendMessage, IClientChannel channel, CancellationToken cancellationToken = default)
|
||||||
{
|
{
|
||||||
try
|
return SendThenReturn(this, sendMessage, channel, cancellationToken);
|
||||||
|
|
||||||
|
static async PooledValueTask<OperResult<ReadOnlyMemory<byte>>> SendThenReturn(DeviceBase @this, ISendMessage sendMessage, IClientChannel channel, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
var result = await SendThenReturnMessageAsync(sendMessage, channel, cancellationToken).ConfigureAwait(false);
|
try
|
||||||
return new OperResult<ReadOnlyMemory<byte>>(result) { Content = result.Content };
|
{
|
||||||
}
|
var result = await @this.SendThenReturnMessageAsync(sendMessage, channel, cancellationToken).ConfigureAwait(false);
|
||||||
catch (Exception ex)
|
return new OperResult<ReadOnlyMemory<byte>>(result) { Content = result.Content };
|
||||||
{
|
}
|
||||||
return new(ex);
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
return new(ex);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -523,48 +544,83 @@ public abstract class DeviceBase : AsyncAndSyncDisposableObject, IDevice
|
|||||||
return GetResponsedDataAsync(command, clientChannel, Timeout, cancellationToken);
|
return GetResponsedDataAsync(command, clientChannel, Timeout, cancellationToken);
|
||||||
}
|
}
|
||||||
|
|
||||||
private ObjectPool<ReusableCancellationTokenSource> _reusableTimeouts = new();
|
private ObjectPoolLock<ReusableCancellationTokenSource> _reusableTimeouts = new();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 发送并等待数据
|
/// 发送并等待数据
|
||||||
/// </summary>
|
/// </summary>
|
||||||
protected async ValueTask<MessageBase> GetResponsedDataAsync(
|
protected ValueTask<MessageBase> GetResponsedDataAsync(
|
||||||
ISendMessage command,
|
ISendMessage command,
|
||||||
IClientChannel clientChannel,
|
IClientChannel clientChannel,
|
||||||
int timeout = 3000,
|
int timeout = 3000,
|
||||||
CancellationToken cancellationToken = default)
|
CancellationToken cancellationToken = default)
|
||||||
{
|
{
|
||||||
var waitData = clientChannel.WaitHandlePool.GetWaitDataAsync(out var sign);
|
return GetResponsedDataAsync(this, command, clientChannel, timeout, cancellationToken);
|
||||||
command.Sign = sign;
|
|
||||||
WaitLock? waitLock = null;
|
|
||||||
|
|
||||||
try
|
static async PooledValueTask<MessageBase> GetResponsedDataAsync(DeviceBase @this, ISendMessage command, IClientChannel clientChannel, int timeout, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
await BeforeSendAsync(clientChannel, cancellationToken).ConfigureAwait(false);
|
var waitData = clientChannel.WaitHandlePool.GetWaitDataAsync(out var sign);
|
||||||
|
command.Sign = sign;
|
||||||
|
WaitLock? waitLock = null;
|
||||||
|
|
||||||
waitLock = GetWaitLock(clientChannel);
|
|
||||||
|
|
||||||
await waitLock.WaitAsync(cancellationToken).ConfigureAwait(false);
|
|
||||||
|
|
||||||
clientChannel.SetDataHandlingAdapterLogger(Logger);
|
|
||||||
|
|
||||||
await SendAsync(command, clientChannel, cancellationToken).ConfigureAwait(false);
|
|
||||||
|
|
||||||
if (waitData.Status == WaitDataStatus.Success)
|
|
||||||
return waitData.CompletedData;
|
|
||||||
|
|
||||||
var reusableTimeout = _reusableTimeouts.Get();
|
|
||||||
var cts = reusableTimeout.GetTokenSource(timeout, cancellationToken, Channel.ClosedToken);
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
await @this.BeforeSendAsync(clientChannel, cancellationToken).ConfigureAwait(false);
|
||||||
|
|
||||||
await waitData.WaitAsync(cts.Token).ConfigureAwait(false);
|
waitLock = @this.GetWaitLock(clientChannel);
|
||||||
|
|
||||||
}
|
await waitLock.WaitAsync(cancellationToken).ConfigureAwait(false);
|
||||||
catch (OperationCanceledException)
|
|
||||||
{
|
clientChannel.SetDataHandlingAdapterLogger(@this.Logger);
|
||||||
return reusableTimeout.TimeoutStatus
|
|
||||||
? new MessageBase(new TimeoutException()) { ErrorMessage = $"Timeout, sign: {sign}" }
|
await @this.SendAsync(command, clientChannel, cancellationToken).ConfigureAwait(false);
|
||||||
: new MessageBase(new OperationCanceledException());
|
|
||||||
|
if (waitData.Status == WaitDataStatus.Success)
|
||||||
|
return waitData.CompletedData;
|
||||||
|
|
||||||
|
var reusableTimeout = @this._reusableTimeouts.Get();
|
||||||
|
try
|
||||||
|
{
|
||||||
|
|
||||||
|
var ctsToken = reusableTimeout.GetTokenSource(timeout, cancellationToken, @this.Channel.ClosedToken);
|
||||||
|
await waitData.WaitAsync(ctsToken).ConfigureAwait(false);
|
||||||
|
|
||||||
|
}
|
||||||
|
catch (OperationCanceledException)
|
||||||
|
{
|
||||||
|
return reusableTimeout.TimeoutStatus
|
||||||
|
? new MessageBase(new TimeoutException()) { ErrorMessage = $"Timeout, sign: {sign}" }
|
||||||
|
: new MessageBase(new OperationCanceledException());
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
return new MessageBase(ex);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
reusableTimeout.Set();
|
||||||
|
@this._reusableTimeouts.Return(reusableTimeout);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (waitData.Status == WaitDataStatus.Success)
|
||||||
|
{
|
||||||
|
return waitData.CompletedData;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var operResult = waitData.Check(reusableTimeout.TimeoutStatus);
|
||||||
|
if (waitData.CompletedData != null)
|
||||||
|
{
|
||||||
|
waitData.CompletedData.ErrorMessage = $"{operResult.ErrorMessage}, sign: {sign}";
|
||||||
|
return waitData.CompletedData;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return new MessageBase(new OperationCanceledException());
|
||||||
|
}
|
||||||
|
|
||||||
|
//return new MessageBase(operResult) { ErrorMessage = $"{operResult.ErrorMessage}, sign: {sign}" };
|
||||||
|
}
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
@@ -572,39 +628,10 @@ public abstract class DeviceBase : AsyncAndSyncDisposableObject, IDevice
|
|||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
reusableTimeout.Set();
|
waitLock?.Release();
|
||||||
_reusableTimeouts.Return(reusableTimeout);
|
waitData?.SafeDispose();
|
||||||
}
|
|
||||||
|
|
||||||
if (waitData.Status == WaitDataStatus.Success)
|
|
||||||
{
|
|
||||||
return waitData.CompletedData;
|
|
||||||
}
|
}
|
||||||
else
|
|
||||||
{
|
|
||||||
var operResult = waitData.Check(reusableTimeout.TimeoutStatus);
|
|
||||||
if(waitData.CompletedData!=null)
|
|
||||||
{
|
|
||||||
waitData.CompletedData.ErrorMessage = $"{operResult.ErrorMessage}, sign: {sign}";
|
|
||||||
return waitData.CompletedData;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
return new MessageBase(new OperationCanceledException());
|
|
||||||
}
|
|
||||||
|
|
||||||
//return new MessageBase(operResult) { ErrorMessage = $"{operResult.ErrorMessage}, sign: {sign}" };
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
return new MessageBase(ex);
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
waitLock?.Release();
|
|
||||||
waitData?.SafeDispose();
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -646,54 +673,59 @@ public abstract class DeviceBase : AsyncAndSyncDisposableObject, IDevice
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public virtual async ValueTask<OperResult> WriteJTokenAsync(string address, JToken value, DataTypeEnum dataType, CancellationToken cancellationToken = default)
|
public virtual ValueTask<OperResult> WriteJTokenAsync(string address, JToken value, DataTypeEnum dataType, CancellationToken cancellationToken = default)
|
||||||
{
|
{
|
||||||
try
|
return WriteJTokenAsync(this, address, value, dataType, cancellationToken);
|
||||||
|
|
||||||
|
static async PooledValueTask<OperResult> WriteJTokenAsync(DeviceBase @this, string address, JToken value, DataTypeEnum dataType, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
var bitConverter = ThingsGatewayBitConverter.GetTransByAddress(address);
|
try
|
||||||
if (value is JArray jArray)
|
|
||||||
{
|
{
|
||||||
return dataType switch
|
var bitConverter = @this.ThingsGatewayBitConverter.GetTransByAddress(address);
|
||||||
|
if (value is JArray jArray)
|
||||||
{
|
{
|
||||||
DataTypeEnum.String => await WriteAsync(address, jArray.ToObject<String[]>().AsMemory(), cancellationToken: cancellationToken).ConfigureAwait(false),
|
return dataType switch
|
||||||
DataTypeEnum.Boolean => await WriteAsync(address, jArray.ToObject<Boolean[]>().AsMemory(), cancellationToken).ConfigureAwait(false),
|
{
|
||||||
DataTypeEnum.Byte => await WriteAsync(address, jArray.ToObject<Byte[]>().AsMemory(), dataType, cancellationToken).ConfigureAwait(false),
|
DataTypeEnum.String => await @this.WriteAsync(address, jArray.ToObject<String[]>().AsMemory(), cancellationToken: cancellationToken).ConfigureAwait(false),
|
||||||
DataTypeEnum.Int16 => await WriteAsync(address, jArray.ToObject<Int16[]>().AsMemory(), cancellationToken: cancellationToken).ConfigureAwait(false),
|
DataTypeEnum.Boolean => await @this.WriteAsync(address, jArray.ToObject<Boolean[]>().AsMemory(), cancellationToken).ConfigureAwait(false),
|
||||||
DataTypeEnum.UInt16 => await WriteAsync(address, jArray.ToObject<UInt16[]>().AsMemory(), cancellationToken: cancellationToken).ConfigureAwait(false),
|
DataTypeEnum.Byte => await @this.WriteAsync(address, jArray.ToObject<Byte[]>().AsMemory(), dataType, cancellationToken).ConfigureAwait(false),
|
||||||
DataTypeEnum.Int32 => await WriteAsync(address, jArray.ToObject<Int32[]>().AsMemory(), cancellationToken: cancellationToken).ConfigureAwait(false),
|
DataTypeEnum.Int16 => await @this.WriteAsync(address, jArray.ToObject<Int16[]>().AsMemory(), cancellationToken: cancellationToken).ConfigureAwait(false),
|
||||||
DataTypeEnum.UInt32 => await WriteAsync(address, jArray.ToObject<UInt32[]>().AsMemory(), cancellationToken: cancellationToken).ConfigureAwait(false),
|
DataTypeEnum.UInt16 => await @this.WriteAsync(address, jArray.ToObject<UInt16[]>().AsMemory(), cancellationToken: cancellationToken).ConfigureAwait(false),
|
||||||
DataTypeEnum.Int64 => await WriteAsync(address, jArray.ToObject<Int64[]>().AsMemory(), cancellationToken: cancellationToken).ConfigureAwait(false),
|
DataTypeEnum.Int32 => await @this.WriteAsync(address, jArray.ToObject<Int32[]>().AsMemory(), cancellationToken: cancellationToken).ConfigureAwait(false),
|
||||||
DataTypeEnum.UInt64 => await WriteAsync(address, jArray.ToObject<UInt64[]>().AsMemory(), cancellationToken: cancellationToken).ConfigureAwait(false),
|
DataTypeEnum.UInt32 => await @this.WriteAsync(address, jArray.ToObject<UInt32[]>().AsMemory(), cancellationToken: cancellationToken).ConfigureAwait(false),
|
||||||
DataTypeEnum.Float => await WriteAsync(address, jArray.ToObject<Single[]>().AsMemory(), cancellationToken: cancellationToken).ConfigureAwait(false),
|
DataTypeEnum.Int64 => await @this.WriteAsync(address, jArray.ToObject<Int64[]>().AsMemory(), cancellationToken: cancellationToken).ConfigureAwait(false),
|
||||||
DataTypeEnum.Double => await WriteAsync(address, jArray.ToObject<Double[]>().AsMemory(), cancellationToken: cancellationToken).ConfigureAwait(false),
|
DataTypeEnum.UInt64 => await @this.WriteAsync(address, jArray.ToObject<UInt64[]>().AsMemory(), cancellationToken: cancellationToken).ConfigureAwait(false),
|
||||||
DataTypeEnum.Decimal => await WriteAsync(address, jArray.ToObject<Decimal[]>().AsMemory(), cancellationToken: cancellationToken).ConfigureAwait(false),
|
DataTypeEnum.Float => await @this.WriteAsync(address, jArray.ToObject<Single[]>().AsMemory(), cancellationToken: cancellationToken).ConfigureAwait(false),
|
||||||
_ => new OperResult(string.Format(AppResource.DataTypeNotSupported, dataType)),
|
DataTypeEnum.Double => await @this.WriteAsync(address, jArray.ToObject<Double[]>().AsMemory(), cancellationToken: cancellationToken).ConfigureAwait(false),
|
||||||
};
|
DataTypeEnum.Decimal => await @this.WriteAsync(address, jArray.ToObject<Decimal[]>().AsMemory(), cancellationToken: cancellationToken).ConfigureAwait(false),
|
||||||
|
_ => new OperResult(string.Format(AppResource.DataTypeNotSupported, dataType)),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return dataType switch
|
||||||
|
{
|
||||||
|
DataTypeEnum.String => await @this.WriteAsync(address, value.ToObject<String>(), bitConverter, cancellationToken).ConfigureAwait(false),
|
||||||
|
DataTypeEnum.Boolean => await @this.WriteAsync(address, value.ToObject<Boolean>(), bitConverter, cancellationToken).ConfigureAwait(false),
|
||||||
|
DataTypeEnum.Byte => await @this.WriteAsync(address, value.ToObject<Byte>(), bitConverter, cancellationToken).ConfigureAwait(false),
|
||||||
|
DataTypeEnum.Int16 => await @this.WriteAsync(address, value.ToObject<Int16>(), bitConverter, cancellationToken).ConfigureAwait(false),
|
||||||
|
DataTypeEnum.UInt16 => await @this.WriteAsync(address, value.ToObject<UInt16>(), bitConverter, cancellationToken).ConfigureAwait(false),
|
||||||
|
DataTypeEnum.Int32 => await @this.WriteAsync(address, value.ToObject<Int32>(), bitConverter, cancellationToken).ConfigureAwait(false),
|
||||||
|
DataTypeEnum.UInt32 => await @this.WriteAsync(address, value.ToObject<UInt32>(), bitConverter, cancellationToken).ConfigureAwait(false),
|
||||||
|
DataTypeEnum.Int64 => await @this.WriteAsync(address, value.ToObject<Int64>(), bitConverter, cancellationToken).ConfigureAwait(false),
|
||||||
|
DataTypeEnum.UInt64 => await @this.WriteAsync(address, value.ToObject<UInt64>(), bitConverter, cancellationToken).ConfigureAwait(false),
|
||||||
|
DataTypeEnum.Float => await @this.WriteAsync(address, value.ToObject<Single>(), bitConverter, cancellationToken).ConfigureAwait(false),
|
||||||
|
DataTypeEnum.Double => await @this.WriteAsync(address, value.ToObject<Double>(), bitConverter, cancellationToken).ConfigureAwait(false),
|
||||||
|
DataTypeEnum.Decimal => await @this.WriteAsync(address, value.ToObject<Decimal>(), bitConverter, cancellationToken).ConfigureAwait(false),
|
||||||
|
_ => new OperResult(string.Format(AppResource.DataTypeNotSupported, dataType)),
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
return dataType switch
|
return new OperResult(ex);
|
||||||
{
|
|
||||||
DataTypeEnum.String => await WriteAsync(address, value.ToObject<String>(), bitConverter, cancellationToken).ConfigureAwait(false),
|
|
||||||
DataTypeEnum.Boolean => await WriteAsync(address, value.ToObject<Boolean>(), bitConverter, cancellationToken).ConfigureAwait(false),
|
|
||||||
DataTypeEnum.Byte => await WriteAsync(address, value.ToObject<Byte>(), bitConverter, cancellationToken).ConfigureAwait(false),
|
|
||||||
DataTypeEnum.Int16 => await WriteAsync(address, value.ToObject<Int16>(), bitConverter, cancellationToken).ConfigureAwait(false),
|
|
||||||
DataTypeEnum.UInt16 => await WriteAsync(address, value.ToObject<UInt16>(), bitConverter, cancellationToken).ConfigureAwait(false),
|
|
||||||
DataTypeEnum.Int32 => await WriteAsync(address, value.ToObject<Int32>(), bitConverter, cancellationToken).ConfigureAwait(false),
|
|
||||||
DataTypeEnum.UInt32 => await WriteAsync(address, value.ToObject<UInt32>(), bitConverter, cancellationToken).ConfigureAwait(false),
|
|
||||||
DataTypeEnum.Int64 => await WriteAsync(address, value.ToObject<Int64>(), bitConverter, cancellationToken).ConfigureAwait(false),
|
|
||||||
DataTypeEnum.UInt64 => await WriteAsync(address, value.ToObject<UInt64>(), bitConverter, cancellationToken).ConfigureAwait(false),
|
|
||||||
DataTypeEnum.Float => await WriteAsync(address, value.ToObject<Single>(), bitConverter, cancellationToken).ConfigureAwait(false),
|
|
||||||
DataTypeEnum.Double => await WriteAsync(address, value.ToObject<Double>(), bitConverter, cancellationToken).ConfigureAwait(false),
|
|
||||||
DataTypeEnum.Decimal => await WriteAsync(address, value.ToObject<Decimal>(), bitConverter, cancellationToken).ConfigureAwait(false),
|
|
||||||
_ => new OperResult(string.Format(AppResource.DataTypeNotSupported, dataType)),
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
return new OperResult(ex);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#endregion 动态类型读写
|
#endregion 动态类型读写
|
||||||
|
|||||||
@@ -102,33 +102,77 @@ public static partial class DeviceExtension
|
|||||||
public static OperResult PraseStructContent<T>(this IEnumerable<T> variables, IDevice device, ReadOnlySpan<byte> buffer, bool exWhenAny) where T : IVariable
|
public static OperResult PraseStructContent<T>(this IEnumerable<T> variables, IDevice device, ReadOnlySpan<byte> buffer, bool exWhenAny) where T : IVariable
|
||||||
{
|
{
|
||||||
var time = DateTime.Now;
|
var time = DateTime.Now;
|
||||||
var result = OperResult.Success;
|
if (variables is IList<T> collection)
|
||||||
foreach (var variable in variables)
|
|
||||||
{
|
{
|
||||||
IThingsGatewayBitConverter byteConverter = variable.ThingsGatewayBitConverter;
|
return PraseCollection(collection, device, buffer, exWhenAny, time);
|
||||||
var dataType = variable.DataType;
|
}
|
||||||
int index = variable.Index;
|
else
|
||||||
try
|
{
|
||||||
{
|
return PraseEnumerable(variables, device, buffer, exWhenAny, time);
|
||||||
var changed = byteConverter.GetChangedDataFormBytes(device, variable.RegisterAddress, buffer, index, dataType, variable.ArrayLength ?? 1, variable.RawValue, out var data);
|
|
||||||
if (changed)
|
}
|
||||||
{
|
static OperResult PraseEnumerable(IEnumerable<T> variables, IDevice device, ReadOnlySpan<byte> buffer, bool exWhenAny, DateTime time)
|
||||||
result = variable.SetValue(data, time);
|
{
|
||||||
if (exWhenAny)
|
foreach (var variable in variables)
|
||||||
if (!result.IsSuccess)
|
{
|
||||||
return result;
|
IThingsGatewayBitConverter byteConverter = variable.ThingsGatewayBitConverter;
|
||||||
}
|
var dataType = variable.DataType;
|
||||||
else
|
int index = variable.Index;
|
||||||
{
|
try
|
||||||
variable.SetNoChangedValue(time);
|
{
|
||||||
}
|
var changed = byteConverter.GetChangedDataFormBytes(device, variable.RegisterAddress, buffer, index, dataType, variable.ArrayLength ?? 1, variable.RawValue, out var data);
|
||||||
}
|
if (changed)
|
||||||
catch (Exception ex)
|
{
|
||||||
{
|
var result = variable.SetValue(data, time);
|
||||||
return new OperResult($"Error parsing byte array, address: {variable.RegisterAddress}, array length: {buffer.Length}, index: {index}, type: {dataType}", ex);
|
if (exWhenAny)
|
||||||
}
|
if (!result.IsSuccess)
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
variable.SetNoChangedValue(time);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
return new OperResult($"Error parsing byte array, address: {variable.RegisterAddress}, array length: {buffer.Length}, index: {index}, type: {dataType}", ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return OperResult.Success;
|
||||||
|
}
|
||||||
|
static OperResult PraseCollection(IList<T> variables, IDevice device, ReadOnlySpan<byte> buffer, bool exWhenAny, DateTime time)
|
||||||
|
{
|
||||||
|
for (int i = 0; i < variables.Count; i++)
|
||||||
|
{
|
||||||
|
var variable = variables[i];
|
||||||
|
|
||||||
|
IThingsGatewayBitConverter byteConverter = variable.ThingsGatewayBitConverter;
|
||||||
|
var dataType = variable.DataType;
|
||||||
|
int index = variable.Index;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var changed = byteConverter.GetChangedDataFormBytes(device, variable.RegisterAddress, buffer, index, dataType, variable.ArrayLength ?? 1, variable.RawValue, out var data);
|
||||||
|
if (changed)
|
||||||
|
{
|
||||||
|
var result = variable.SetValue(data, time);
|
||||||
|
if (exWhenAny)
|
||||||
|
if (!result.IsSuccess)
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
variable.SetNoChangedValue(time);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
return new OperResult($"Error parsing byte array, address: {variable.RegisterAddress}, array length: {buffer.Length}, index: {index}, type: {dataType}", ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return OperResult.Success;
|
||||||
}
|
}
|
||||||
return result;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|||||||
@@ -8,6 +8,8 @@
|
|||||||
// QQ群:605534569
|
// QQ群:605534569
|
||||||
//------------------------------------------------------------------------------
|
//------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
using PooledAwait;
|
||||||
|
|
||||||
using TouchSocket.Core;
|
using TouchSocket.Core;
|
||||||
|
|
||||||
namespace ThingsGateway.Gateway.Application;
|
namespace ThingsGateway.Gateway.Application;
|
||||||
@@ -28,22 +30,26 @@ public class AsyncReadWriteLock : IAsyncDisposable
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// 获取读锁,支持多个线程并发读取,但写入时会阻止所有读取。
|
/// 获取读锁,支持多个线程并发读取,但写入时会阻止所有读取。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public async ValueTask<CancellationToken> ReaderLockAsync(CancellationToken cancellationToken)
|
public ValueTask<CancellationToken> ReaderLockAsync(CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
|
return ReaderLockAsync(this, cancellationToken);
|
||||||
|
|
||||||
if (Interlocked.Read(ref _writerCount) > 0)
|
static async PooledValueTask<CancellationToken> ReaderLockAsync(AsyncReadWriteLock @this, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
Interlocked.Increment(ref _readerCount);
|
if (Interlocked.Read(ref @this._writerCount) > 0)
|
||||||
|
{
|
||||||
|
Interlocked.Increment(ref @this._readerCount);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// 第一个读者需要获取写入锁,防止写操作
|
// 第一个读者需要获取写入锁,防止写操作
|
||||||
await _readerLock.WaitOneAsync(cancellationToken).ConfigureAwait(false);
|
await @this._readerLock.WaitOneAsync(cancellationToken).ConfigureAwait(false);
|
||||||
|
|
||||||
Interlocked.Decrement(ref _readerCount);
|
Interlocked.Decrement(ref @this._readerCount);
|
||||||
|
|
||||||
|
}
|
||||||
|
return @this._cancellationTokenSource.Token;
|
||||||
}
|
}
|
||||||
return _cancellationTokenSource.Token;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool WriteWaited => _writerCount > 0;
|
public bool WriteWaited => _writerCount > 0;
|
||||||
@@ -51,21 +57,25 @@ public class AsyncReadWriteLock : IAsyncDisposable
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// 获取写锁,阻止所有读取。
|
/// 获取写锁,阻止所有读取。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public async ValueTask<IDisposable> WriterLockAsync(CancellationToken cancellationToken)
|
public ValueTask<IDisposable> WriterLockAsync(CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
|
return WriterLockAsync(this);
|
||||||
|
|
||||||
if (Interlocked.Increment(ref _writerCount) == 1)
|
static async PooledValueTask<IDisposable> WriterLockAsync(AsyncReadWriteLock @this)
|
||||||
{
|
{
|
||||||
if (_writePriority)
|
if (Interlocked.Increment(ref @this._writerCount) == 1)
|
||||||
{
|
{
|
||||||
var cancellationTokenSource = _cancellationTokenSource;
|
if (@this._writePriority)
|
||||||
_cancellationTokenSource = new();
|
{
|
||||||
await cancellationTokenSource.SafeCancelAsync().ConfigureAwait(false); // 取消读取
|
var cancellationTokenSource = @this._cancellationTokenSource;
|
||||||
cancellationTokenSource.SafeDispose();
|
@this._cancellationTokenSource = new();
|
||||||
|
await cancellationTokenSource.SafeCancelAsync().ConfigureAwait(false); // 取消读取
|
||||||
|
cancellationTokenSource.SafeDispose();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
return new Writer(this);
|
return new Writer(@this);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
private object lockObject = new();
|
private object lockObject = new();
|
||||||
private void ReleaseWriter()
|
private void ReleaseWriter()
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
using ThingsGateway.NewLife;
|
using PooledAwait;
|
||||||
|
|
||||||
|
using ThingsGateway.NewLife;
|
||||||
using ThingsGateway.NewLife.Threading;
|
using ThingsGateway.NewLife.Threading;
|
||||||
|
|
||||||
using TouchSocket.Core;
|
using TouchSocket.Core;
|
||||||
@@ -67,47 +69,52 @@ public class CronScheduledTask : DisposeBase, IScheduledTask
|
|||||||
_timer = new TimerX(TimerCallbackAsync, _state, _interval, nameof(CronScheduledTask)) { Async = true, Reentrant = false };
|
_timer = new TimerX(TimerCallbackAsync, _state, _interval, nameof(CronScheduledTask)) { Async = true, Reentrant = false };
|
||||||
}
|
}
|
||||||
|
|
||||||
private async ValueTask TimerCallbackAsync(object? state)
|
private ValueTask TimerCallbackAsync(object? state)
|
||||||
{
|
{
|
||||||
if (Check()) return;
|
return TimerCallbackAsync(this, state);
|
||||||
if (_taskFunc == null && _valueTaskFunc == null)
|
static async PooledValueTask TimerCallbackAsync(CronScheduledTask @this, object? state)
|
||||||
{
|
{
|
||||||
Dispose();
|
if (@this.Check()) return;
|
||||||
return;
|
if (@this._taskFunc == null && @this._valueTaskFunc == null)
|
||||||
}
|
|
||||||
|
|
||||||
Interlocked.Increment(ref _pendingTriggers);
|
|
||||||
|
|
||||||
if (Interlocked.Exchange(ref _isRunning, 1) == 1)
|
|
||||||
return;
|
|
||||||
|
|
||||||
// 减少一个触发次数
|
|
||||||
Interlocked.Decrement(ref _pendingTriggers);
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
if (_taskFunc != null)
|
|
||||||
await _taskFunc(state, _token).ConfigureAwait(false);
|
|
||||||
else if (_valueTaskFunc != null)
|
|
||||||
await _valueTaskFunc(state, _token).ConfigureAwait(false);
|
|
||||||
}
|
|
||||||
catch (OperationCanceledException)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
LogMessage?.LogWarning(ex);
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
Interlocked.Exchange(ref _isRunning, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (Interlocked.Exchange(ref _pendingTriggers, 0) >= 1)
|
|
||||||
{
|
|
||||||
if (!Check())
|
|
||||||
{
|
{
|
||||||
SetNext(next);
|
@this.Dispose();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Interlocked.Increment(ref @this._pendingTriggers);
|
||||||
|
|
||||||
|
if (Interlocked.Exchange(ref @this._isRunning, 1) == 1)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// 减少一个触发次数
|
||||||
|
Interlocked.Decrement(ref @this._pendingTriggers);
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (@this._taskFunc != null)
|
||||||
|
await @this._taskFunc(state, @this._token).ConfigureAwait(false);
|
||||||
|
else if (@this._valueTaskFunc != null)
|
||||||
|
await @this._valueTaskFunc(state, @this._token).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
catch (OperationCanceledException)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
@this.LogMessage?.LogWarning(ex);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
Interlocked.Exchange(ref @this._isRunning, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Interlocked.Exchange(ref @this._pendingTriggers, 0) >= 1)
|
||||||
|
{
|
||||||
|
if (!@this.Check())
|
||||||
|
{
|
||||||
|
int nextValue = @this.next;
|
||||||
|
@this.SetNext(nextValue);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -149,7 +156,8 @@ public class CronScheduledTask : DisposeBase, IScheduledTask
|
|||||||
{
|
{
|
||||||
if (!Check())
|
if (!Check())
|
||||||
{
|
{
|
||||||
SetNext(next);
|
int nextValue = next;
|
||||||
|
SetNext(nextValue);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,146 +0,0 @@
|
|||||||
////------------------------------------------------------------------------------
|
|
||||||
//// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
|
|
||||||
//// 此代码版权(除特别声明外的代码)归作者本人Diego所有
|
|
||||||
//// 源代码使用协议遵循本仓库的开源协议及附加协议
|
|
||||||
//// Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway
|
|
||||||
//// Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway
|
|
||||||
//// 使用文档:https://thingsgateway.cn/
|
|
||||||
//// QQ群:605534569
|
|
||||||
////------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
//using Microsoft.Extensions.Logging;
|
|
||||||
|
|
||||||
//using ThingsGateway.NewLife;
|
|
||||||
|
|
||||||
//using TouchSocket.Core;
|
|
||||||
|
|
||||||
//namespace ThingsGateway.Gateway.Application;
|
|
||||||
|
|
||||||
//[ThingsGateway.DependencyInjection.SuppressSniffer]
|
|
||||||
//public class DoTask
|
|
||||||
//{
|
|
||||||
// /// <summary>
|
|
||||||
// /// 取消令牌
|
|
||||||
// /// </summary>
|
|
||||||
// private CancellationTokenSource? _cancelTokenSource;
|
|
||||||
// private object? _state;
|
|
||||||
|
|
||||||
// public DoTask(Func<object?, CancellationToken, Task> doWork, ILog logger, object? state = null, string taskName = null)
|
|
||||||
// {
|
|
||||||
// DoWork = doWork; Logger = logger; TaskName = taskName; _state = state;
|
|
||||||
// }
|
|
||||||
|
|
||||||
// /// <summary>
|
|
||||||
// /// 执行任务方法
|
|
||||||
// /// </summary>
|
|
||||||
// public Func<object?, CancellationToken, Task> DoWork { get; }
|
|
||||||
// private ILog Logger { get; }
|
|
||||||
// private Task PrivateTask { get; set; }
|
|
||||||
// private string TaskName { get; }
|
|
||||||
|
|
||||||
// /// <summary>
|
|
||||||
// /// 开始
|
|
||||||
// /// </summary>
|
|
||||||
// /// <param name="cancellationToken">调度取消令牌</param>
|
|
||||||
// public void Start(CancellationToken cancellationToken)
|
|
||||||
// {
|
|
||||||
// try
|
|
||||||
// {
|
|
||||||
// WaitLock.Wait(cancellationToken);
|
|
||||||
|
|
||||||
// if (cancellationToken.CanBeCanceled)
|
|
||||||
// {
|
|
||||||
// _cancelTokenSource = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
|
|
||||||
// }
|
|
||||||
// else
|
|
||||||
// {
|
|
||||||
// _cancelTokenSource = new CancellationTokenSource();
|
|
||||||
// }
|
|
||||||
|
|
||||||
// // 异步执行
|
|
||||||
// PrivateTask = Do();
|
|
||||||
// }
|
|
||||||
// finally
|
|
||||||
// {
|
|
||||||
// WaitLock.Release();
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
// private async Task Do()
|
|
||||||
// {
|
|
||||||
// await Task.Yield();
|
|
||||||
// while (!_cancelTokenSource.IsCancellationRequested)
|
|
||||||
// {
|
|
||||||
// try
|
|
||||||
// {
|
|
||||||
// if (_cancelTokenSource.IsCancellationRequested)
|
|
||||||
// return;
|
|
||||||
// await DoWork(_state, _cancelTokenSource.Token).ConfigureAwait(false);
|
|
||||||
// }
|
|
||||||
// catch (OperationCanceledException)
|
|
||||||
// {
|
|
||||||
// }
|
|
||||||
// catch (ObjectDisposedException)
|
|
||||||
// {
|
|
||||||
// }
|
|
||||||
// catch (Exception ex)
|
|
||||||
// {
|
|
||||||
// Logger?.LogWarning(ex, "DoWork");
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
// private WaitLock WaitLock = new();
|
|
||||||
// /// <summary>
|
|
||||||
// /// 停止操作
|
|
||||||
// /// </summary>
|
|
||||||
// public async Task StopAsync(TimeSpan? waitTime = null)
|
|
||||||
// {
|
|
||||||
// try
|
|
||||||
// {
|
|
||||||
// await WaitLock.WaitAsync().ConfigureAwait(false);
|
|
||||||
|
|
||||||
// try
|
|
||||||
// {
|
|
||||||
// _cancelTokenSource?.Cancel();
|
|
||||||
// _cancelTokenSource?.Dispose();
|
|
||||||
// }
|
|
||||||
// catch (Exception ex)
|
|
||||||
// {
|
|
||||||
// Logger?.LogWarning(ex, "Cancel error");
|
|
||||||
// }
|
|
||||||
|
|
||||||
// if (PrivateTask != null)
|
|
||||||
// {
|
|
||||||
// try
|
|
||||||
// {
|
|
||||||
// if (TaskName != null)
|
|
||||||
// Logger?.LogInformation($"{TaskName} Stoping");
|
|
||||||
// if (waitTime != null)
|
|
||||||
// await PrivateTask.WaitAsync(waitTime.Value).ConfigureAwait(false);
|
|
||||||
// if (TaskName != null)
|
|
||||||
// Logger?.LogInformation($"{TaskName} Stoped");
|
|
||||||
// }
|
|
||||||
// catch (ObjectDisposedException)
|
|
||||||
// {
|
|
||||||
// }
|
|
||||||
// catch (TimeoutException)
|
|
||||||
// {
|
|
||||||
// if (TaskName != null)
|
|
||||||
// Logger?.LogWarning($"{TaskName} Stop timeout, exiting wait block");
|
|
||||||
// }
|
|
||||||
// catch (Exception ex)
|
|
||||||
// {
|
|
||||||
// if (TaskName != null)
|
|
||||||
// Logger?.LogWarning(ex, $"{TaskName} Stop error");
|
|
||||||
// }
|
|
||||||
// PrivateTask = null;
|
|
||||||
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// finally
|
|
||||||
// {
|
|
||||||
// WaitLock.Release();
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
//}
|
|
||||||
@@ -1,4 +1,6 @@
|
|||||||
using ThingsGateway.NewLife;
|
using PooledAwait;
|
||||||
|
|
||||||
|
using ThingsGateway.NewLife;
|
||||||
using ThingsGateway.NewLife.Threading;
|
using ThingsGateway.NewLife.Threading;
|
||||||
|
|
||||||
using TouchSocket.Core;
|
using TouchSocket.Core;
|
||||||
@@ -51,49 +53,54 @@ public class ScheduledAsyncTask : DisposeBase, IScheduledTask, IScheduledIntInte
|
|||||||
_timer = new TimerX(DoAsync, _state, IntervalMS, IntervalMS, nameof(ScheduledAsyncTask)) { Async = true, Reentrant = false };
|
_timer = new TimerX(DoAsync, _state, IntervalMS, IntervalMS, nameof(ScheduledAsyncTask)) { Async = true, Reentrant = false };
|
||||||
}
|
}
|
||||||
|
|
||||||
private async ValueTask DoAsync(object? state)
|
private ValueTask DoAsync(object? state)
|
||||||
{
|
{
|
||||||
if (Check())
|
return DoAsync(this, state);
|
||||||
return;
|
static async PooledValueTask DoAsync(ScheduledAsyncTask @this, object? state)
|
||||||
|
|
||||||
if (_taskFunc == null && _valueTaskFunc == null)
|
|
||||||
{
|
{
|
||||||
Dispose();
|
if (@this.Check())
|
||||||
return;
|
return;
|
||||||
}
|
|
||||||
|
|
||||||
Interlocked.Increment(ref _pendingTriggers);
|
if (@this._taskFunc == null && @this._valueTaskFunc == null)
|
||||||
|
|
||||||
if (Interlocked.Exchange(ref _isRunning, 1) == 1)
|
|
||||||
return;
|
|
||||||
|
|
||||||
// 减少一个触发次数
|
|
||||||
Interlocked.Decrement(ref _pendingTriggers);
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
if (_taskFunc != null)
|
|
||||||
await _taskFunc(state, _token).ConfigureAwait(false);
|
|
||||||
else if (_valueTaskFunc != null)
|
|
||||||
await _valueTaskFunc(state, _token).ConfigureAwait(false);
|
|
||||||
}
|
|
||||||
catch (OperationCanceledException)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
LogMessage?.LogWarning(ex);
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
Interlocked.Exchange(ref _isRunning, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (Interlocked.Exchange(ref _pendingTriggers, 0) >= 1)
|
|
||||||
{
|
|
||||||
if (!Check() && IntervalMS > 8)
|
|
||||||
{
|
{
|
||||||
SetNext(next);
|
@this.Dispose();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Interlocked.Increment(ref @this._pendingTriggers);
|
||||||
|
|
||||||
|
if (Interlocked.Exchange(ref @this._isRunning, 1) == 1)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// 减少一个触发次数
|
||||||
|
Interlocked.Decrement(ref @this._pendingTriggers);
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (@this._taskFunc != null)
|
||||||
|
await @this._taskFunc(state, @this._token).ConfigureAwait(false);
|
||||||
|
else if (@this._valueTaskFunc != null)
|
||||||
|
await @this._valueTaskFunc(state, @this._token).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
catch (OperationCanceledException)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
@this.LogMessage?.LogWarning(ex);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
Interlocked.Exchange(ref @this._isRunning, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Interlocked.Exchange(ref @this._pendingTriggers, 0) >= 1)
|
||||||
|
{
|
||||||
|
if (!@this.Check() && @this.IntervalMS > 8)
|
||||||
|
{
|
||||||
|
int nextValue = @this.next;
|
||||||
|
@this.SetNext(nextValue);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -80,7 +80,8 @@ public class ScheduledSyncTask : DisposeBase, IScheduledTask, IScheduledIntInter
|
|||||||
{
|
{
|
||||||
if (!Check() && IntervalMS > 8)
|
if (!Check() && IntervalMS > 8)
|
||||||
{
|
{
|
||||||
SetNext(next);
|
int nextValue = next;
|
||||||
|
SetNext(nextValue);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ using Microsoft.AspNetCore.Http;
|
|||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
|
||||||
using System.ComponentModel;
|
using System.ComponentModel;
|
||||||
|
using ThingsGateway.NewLife.DictionaryExtensions;
|
||||||
|
|
||||||
using ThingsGateway.FriendlyException;
|
using ThingsGateway.FriendlyException;
|
||||||
|
|
||||||
@@ -122,16 +123,11 @@ public class ControlController : ControllerBase, IRpcServer
|
|||||||
[TouchSocket.WebApi.WebApi(Method = TouchSocket.WebApi.HttpMethodType.Post)]
|
[TouchSocket.WebApi.WebApi(Method = TouchSocket.WebApi.HttpMethodType.Post)]
|
||||||
public async Task<Dictionary<string, Dictionary<string, OperResult>>> WriteVariablesAsync([FromBody][TouchSocket.WebApi.FromBody] Dictionary<string, Dictionary<string, string>> deviceDatas)
|
public async Task<Dictionary<string, Dictionary<string, OperResult>>> WriteVariablesAsync([FromBody][TouchSocket.WebApi.FromBody] Dictionary<string, Dictionary<string, string>> deviceDatas)
|
||||||
{
|
{
|
||||||
foreach (var deviceData in deviceDatas)
|
await GlobalData.CheckByDeviceNames(deviceDatas.Select(a => a.Key)).ConfigureAwait(false);
|
||||||
{
|
|
||||||
if (GlobalData.Devices.TryGetValue(deviceData.Key, out var device))
|
|
||||||
{
|
|
||||||
var data = device.VariableRuntimes.Where(a => deviceData.Value.ContainsKey(a.Key)).ToList();
|
|
||||||
await GlobalData.SysUserService.CheckApiDataScopeAsync(data.Select(a => a.Value.CreateOrgId), data.Select(a => a.Value.CreateUserId)).ConfigureAwait(false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return (await GlobalData.RpcService.InvokeDeviceMethodAsync($"WebApi-{UserManager.UserAccount}-{App.HttpContext?.GetRemoteIpAddressToIPv4()}", deviceDatas).ConfigureAwait(false)).ToDictionary(a => a.Key, a => a.Value.ToDictionary(b => b.Key, b => (OperResult)b.Value));
|
return (await GlobalData.RpcService.InvokeDeviceMethodAsync($"WebApi-{UserManager.UserAccount}-{App.HttpContext?.GetRemoteIpAddressToIPv4()}", deviceDatas).ConfigureAwait(false)).ToDictionary(a => a.Key, a => a.Value.ToDictionary(b => b.Key, b => (OperResult)b.Value));
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -12,12 +12,16 @@ using Microsoft.Extensions.Logging;
|
|||||||
|
|
||||||
using Newtonsoft.Json.Linq;
|
using Newtonsoft.Json.Linq;
|
||||||
|
|
||||||
|
using PooledAwait;
|
||||||
|
|
||||||
using System.Collections.Concurrent;
|
using System.Collections.Concurrent;
|
||||||
|
|
||||||
using ThingsGateway.Common.Extension;
|
using ThingsGateway.Common.Extension;
|
||||||
using ThingsGateway.Extension.Generic;
|
using ThingsGateway.Extension.Generic;
|
||||||
#if !Management
|
#if !Management
|
||||||
using ThingsGateway.Gateway.Application.Extensions;
|
using ThingsGateway.Gateway.Application.Extensions;
|
||||||
|
using ThingsGateway.NewLife;
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
using ThingsGateway.NewLife.Json.Extension;
|
using ThingsGateway.NewLife.Json.Extension;
|
||||||
using ThingsGateway.NewLife.Threading;
|
using ThingsGateway.NewLife.Threading;
|
||||||
@@ -290,199 +294,397 @@ public abstract partial class CollectBase : DriverBase
|
|||||||
|
|
||||||
|
|
||||||
#region 执行方法
|
#region 执行方法
|
||||||
async ValueTask ReadVariableMed(object? state, CancellationToken cancellationToken)
|
ValueTask ReadVariableMed(object? state, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
if (state is not VariableMethod readVariableMethods) return;
|
if (state is not VariableMethod readVariableMethods)
|
||||||
|
return ValueTask.CompletedTask;
|
||||||
if (Pause)
|
if (Pause)
|
||||||
return;
|
return ValueTask.CompletedTask;
|
||||||
if (cancellationToken.IsCancellationRequested)
|
if (cancellationToken.IsCancellationRequested)
|
||||||
return;
|
return ValueTask.CompletedTask;
|
||||||
|
return ReadVariableMed(this, readVariableMethods, cancellationToken);
|
||||||
|
|
||||||
var readErrorCount = 0;
|
static async PooledValueTask ReadVariableMed(CollectBase @this, VariableMethod readVariableMethods, CancellationToken cancellationToken)
|
||||||
|
|
||||||
//if (LogMessage?.LogLevel <= TouchSocket.Core.LogLevel.Trace)
|
|
||||||
// LogMessage?.Trace(string.Format("{0} - Executing method [{1}]", DeviceName, readVariableMethods.MethodInfo.Name));
|
|
||||||
var readResult = await InvokeMethodAsync(readVariableMethods, cancellationToken: cancellationToken).ConfigureAwait(false);
|
|
||||||
|
|
||||||
// 方法调用失败时重试一定次数
|
|
||||||
while (!readResult.IsSuccess && readErrorCount < CollectProperties.RetryCount)
|
|
||||||
{
|
{
|
||||||
if (Pause)
|
|
||||||
return;
|
|
||||||
if (cancellationToken.IsCancellationRequested)
|
|
||||||
return;
|
|
||||||
|
|
||||||
readErrorCount++;
|
var readErrorCount = 0;
|
||||||
if (LogMessage?.LogLevel <= TouchSocket.Core.LogLevel.Trace)
|
|
||||||
LogMessage?.Trace(string.Format("{0} - Execute method [{1}] - failed - {2}", DeviceName, readVariableMethods.MethodInfo.Name, readResult.ErrorMessage));
|
|
||||||
|
|
||||||
//if (LogMessage?.LogLevel <= TouchSocket.Core.LogLevel.Trace)
|
//if (LogMessage?.LogLevel <= TouchSocket.Core.LogLevel.Trace)
|
||||||
// LogMessage?.Trace(string.Format("{0} - Executing method [{1}]", DeviceName, readVariableMethods.MethodInfo.Name));
|
// LogMessage?.Trace(string.Format("{0} - Executing method [{1}]", DeviceName, readVariableMethods.MethodInfo.Name));
|
||||||
readResult = await InvokeMethodAsync(readVariableMethods, cancellationToken: cancellationToken).ConfigureAwait(false);
|
var readResult = await @this.InvokeMethodAsync(readVariableMethods, cancellationToken: cancellationToken).ConfigureAwait(false);
|
||||||
}
|
|
||||||
|
|
||||||
if (readResult.IsSuccess)
|
// 方法调用失败时重试一定次数
|
||||||
{
|
while (!readResult.IsSuccess && readErrorCount < @this.CollectProperties.RetryCount)
|
||||||
// 方法调用成功时记录日志并增加成功计数器
|
|
||||||
if (LogMessage?.LogLevel <= TouchSocket.Core.LogLevel.Trace)
|
|
||||||
LogMessage?.Trace(string.Format("{0} - Execute method [{1}] - Succeeded {2}", DeviceName, readVariableMethods.MethodInfo.Name, readResult.Content?.ToSystemTextJsonString()));
|
|
||||||
CurrentDevice.SetDeviceStatus(TimerX.Now, null);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
if (cancellationToken.IsCancellationRequested)
|
|
||||||
return;
|
|
||||||
|
|
||||||
// 方法调用失败时记录日志并增加失败计数器,更新错误信息
|
|
||||||
if (readVariableMethods.LastErrorMessage != readResult.ErrorMessage)
|
|
||||||
{
|
{
|
||||||
if (!cancellationToken.IsCancellationRequested)
|
if (@this.Pause)
|
||||||
LogMessage?.LogWarning(readResult.Exception, string.Format(AppResource.MethodFail, DeviceName, readVariableMethods.MethodInfo.Name, readResult.ErrorMessage));
|
return;
|
||||||
|
if (cancellationToken.IsCancellationRequested)
|
||||||
|
return;
|
||||||
|
|
||||||
|
readErrorCount++;
|
||||||
|
if (@this.LogMessage?.LogLevel <= TouchSocket.Core.LogLevel.Trace)
|
||||||
|
@this.LogMessage?.Trace(string.Format("{0} - Execute method [{1}] - failed - {2}", @this.DeviceName, readVariableMethods.MethodInfo.Name, readResult.ErrorMessage));
|
||||||
|
|
||||||
|
//if (LogMessage?.LogLevel <= TouchSocket.Core.LogLevel.Trace)
|
||||||
|
// LogMessage?.Trace(string.Format("{0} - Executing method [{1}]", DeviceName, readVariableMethods.MethodInfo.Name));
|
||||||
|
readResult = await @this.InvokeMethodAsync(readVariableMethods, cancellationToken: cancellationToken).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (readResult.IsSuccess)
|
||||||
|
{
|
||||||
|
// 方法调用成功时记录日志并增加成功计数器
|
||||||
|
if (@this.LogMessage?.LogLevel <= TouchSocket.Core.LogLevel.Trace)
|
||||||
|
@this.LogMessage?.Trace(string.Format("{0} - Execute method [{1}] - Succeeded {2}", @this.DeviceName, readVariableMethods.MethodInfo.Name, readResult.Content?.ToSystemTextJsonString()));
|
||||||
|
@this.CurrentDevice.SetDeviceStatus(TimerX.Now, null);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
if (!cancellationToken.IsCancellationRequested)
|
if (cancellationToken.IsCancellationRequested)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// 方法调用失败时记录日志并增加失败计数器,更新错误信息
|
||||||
|
if (readVariableMethods.LastErrorMessage != readResult.ErrorMessage)
|
||||||
{
|
{
|
||||||
if (LogMessage?.LogLevel <= TouchSocket.Core.LogLevel.Trace)
|
if (!cancellationToken.IsCancellationRequested)
|
||||||
LogMessage?.Trace(string.Format("{0} - Execute method [{1}] - failed - {2}", DeviceName, readVariableMethods.MethodInfo.Name, readResult.ErrorMessage));
|
@this.LogMessage?.LogWarning(readResult.Exception, string.Format(AppResource.MethodFail, @this.DeviceName, readVariableMethods.MethodInfo.Name, readResult.ErrorMessage));
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (!cancellationToken.IsCancellationRequested)
|
||||||
|
{
|
||||||
|
if (@this.LogMessage?.LogLevel <= TouchSocket.Core.LogLevel.Trace)
|
||||||
|
@this.LogMessage?.Trace(string.Format("{0} - Execute method [{1}] - failed - {2}", @this.DeviceName, readVariableMethods.MethodInfo.Name, readResult.ErrorMessage));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
readVariableMethods.LastErrorMessage = readResult.ErrorMessage;
|
||||||
|
@this.CurrentDevice.SetDeviceStatus(TimerX.Now, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
readVariableMethods.LastErrorMessage = readResult.ErrorMessage;
|
return;
|
||||||
CurrentDevice.SetDeviceStatus(TimerX.Now, null);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
private readonly LinkedCancellationTokenSourceCache _linkedCtsCache = new();
|
private readonly LinkedCancellationTokenSourceCache _linkedCtsCache = new();
|
||||||
|
|
||||||
#region 执行默认读取
|
#region 执行默认读取
|
||||||
async ValueTask ReadVariableSource(object? state, CancellationToken cancellationToken)
|
ValueTask ReadVariableSource(object? state, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
|
return ReadVariableSource(this, state, cancellationToken);
|
||||||
if (state is not VariableSourceRead variableSourceRead) return;
|
static async PooledValueTask ReadVariableSource(CollectBase @this, object? state, CancellationToken cancellationToken)
|
||||||
|
|
||||||
if (Pause) return;
|
|
||||||
if (cancellationToken.IsCancellationRequested) return;
|
|
||||||
CancellationToken readToken = default;
|
|
||||||
var readerLockTask = ReadWriteLock.ReaderLockAsync(cancellationToken);
|
|
||||||
if (!readerLockTask.IsCompleted)
|
|
||||||
{
|
{
|
||||||
readToken = await readerLockTask.ConfigureAwait(false);
|
if (state is not VariableSourceRead variableSourceRead) return;
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
readToken = readerLockTask.Result;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (readToken.IsCancellationRequested)
|
if (@this.Pause) return;
|
||||||
{
|
if (cancellationToken.IsCancellationRequested) return;
|
||||||
await ReadVariableSource(state, cancellationToken).ConfigureAwait(false);
|
CancellationToken readToken = default;
|
||||||
return;
|
var readerLockTask = @this.ReadWriteLock.ReaderLockAsync(cancellationToken);
|
||||||
}
|
if (!readerLockTask.IsCompleted)
|
||||||
|
{
|
||||||
var allTokenSource = _linkedCtsCache.GetLinkedTokenSource(cancellationToken, readToken);
|
readToken = await readerLockTask.ConfigureAwait(false);
|
||||||
var allToken = allTokenSource.Token;
|
}
|
||||||
|
else
|
||||||
//if (LogMessage?.LogLevel <= TouchSocket.Core.LogLevel.Trace)
|
{
|
||||||
// LogMessage?.Trace(string.Format("{0} - Collecting [{1} - {2}]", DeviceName, variableSourceRead?.RegisterAddress, variableSourceRead?.Length));
|
readToken = readerLockTask.Result;
|
||||||
|
}
|
||||||
OperResult<ReadOnlyMemory<byte>> readResult = default;
|
|
||||||
var readTask = ReadSourceAsync(variableSourceRead, allToken);
|
|
||||||
if (!readTask.IsCompleted)
|
|
||||||
{
|
|
||||||
readResult = await readTask.ConfigureAwait(false);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
readResult = readTask.Result;
|
|
||||||
}
|
|
||||||
|
|
||||||
var readErrorCount = 0;
|
|
||||||
|
|
||||||
// 读取失败时重试一定次数
|
|
||||||
while (!readResult.IsSuccess && readErrorCount < CollectProperties.RetryCount)
|
|
||||||
{
|
|
||||||
if (Pause)
|
|
||||||
return;
|
|
||||||
if (cancellationToken.IsCancellationRequested)
|
|
||||||
return;
|
|
||||||
|
|
||||||
if (readToken.IsCancellationRequested)
|
if (readToken.IsCancellationRequested)
|
||||||
{
|
{
|
||||||
await ReadVariableSource(state, cancellationToken).ConfigureAwait(false);
|
await @this.ReadVariableSource(state, cancellationToken).ConfigureAwait(false);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
readErrorCount++;
|
var allTokenSource = @this._linkedCtsCache.GetLinkedTokenSource(cancellationToken, readToken);
|
||||||
if (LogMessage?.LogLevel <= TouchSocket.Core.LogLevel.Trace)
|
var allToken = allTokenSource.Token;
|
||||||
LogMessage?.Trace(string.Format("{0} - Collection [{1} - {2}] failed - {3}", DeviceName, variableSourceRead?.RegisterAddress, variableSourceRead?.Length, readResult.ErrorMessage));
|
|
||||||
|
|
||||||
//if (LogMessage?.LogLevel <= TouchSocket.Core.LogLevel.Trace)
|
//if (LogMessage?.LogLevel <= TouchSocket.Core.LogLevel.Trace)
|
||||||
// LogMessage?.Trace(string.Format("{0} - Collecting [{1} - {2}]", DeviceName, variableSourceRead?.RegisterAddress, variableSourceRead?.Length));
|
// LogMessage?.Trace(string.Format("{0} - Collecting [{1} - {2}]", DeviceName, variableSourceRead?.RegisterAddress, variableSourceRead?.Length));
|
||||||
var readTask1 = ReadSourceAsync(variableSourceRead, allToken);
|
|
||||||
if (!readTask1.IsCompleted)
|
OperResult<ReadOnlyMemory<byte>> readResult = default;
|
||||||
|
var readTask = @this.ReadSourceAsync(variableSourceRead, allToken);
|
||||||
|
if (!readTask.IsCompleted)
|
||||||
{
|
{
|
||||||
readResult = await readTask1.ConfigureAwait(false);
|
readResult = await readTask.ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
readResult = readTask1.Result;
|
readResult = readTask.Result;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
var readErrorCount = 0;
|
||||||
|
|
||||||
if (readResult.IsSuccess)
|
// 读取失败时重试一定次数
|
||||||
{
|
while (!readResult.IsSuccess && readErrorCount < @this.CollectProperties.RetryCount)
|
||||||
// 读取成功时记录日志并增加成功计数器
|
{
|
||||||
if (LogMessage?.LogLevel <= TouchSocket.Core.LogLevel.Trace)
|
if (@this.Pause)
|
||||||
LogMessage?.Trace(string.Format("{0} - Collection [{1} - {2}] data succeeded {3}", DeviceName, variableSourceRead?.RegisterAddress, variableSourceRead?.Length, readResult.Content.Span.ToHexString(' ')));
|
return;
|
||||||
CurrentDevice.SetDeviceStatus(TimerX.Now, null);
|
if (cancellationToken.IsCancellationRequested)
|
||||||
}
|
return;
|
||||||
else
|
|
||||||
{
|
|
||||||
if (cancellationToken.IsCancellationRequested)
|
|
||||||
return;
|
|
||||||
|
|
||||||
if (readToken.IsCancellationRequested)
|
if (readToken.IsCancellationRequested)
|
||||||
{
|
|
||||||
await ReadVariableSource(state, cancellationToken).ConfigureAwait(false);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 读取失败时记录日志并增加失败计数器,更新错误信息并清除变量状态
|
|
||||||
if (variableSourceRead.LastErrorMessage != readResult.ErrorMessage)
|
|
||||||
{
|
|
||||||
if (!cancellationToken.IsCancellationRequested)
|
|
||||||
LogMessage?.LogWarning(readResult.Exception, string.Format(AppResource.CollectFail, DeviceName, variableSourceRead?.RegisterAddress, variableSourceRead?.Length, readResult.ErrorMessage));
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
if (!cancellationToken.IsCancellationRequested)
|
|
||||||
{
|
{
|
||||||
if (LogMessage?.LogLevel <= TouchSocket.Core.LogLevel.Trace)
|
await @this.ReadVariableSource(state, cancellationToken).ConfigureAwait(false);
|
||||||
LogMessage?.Trace(string.Format("{0} - Collection [{1} - {2}] data failed - {3}", DeviceName, variableSourceRead?.RegisterAddress, variableSourceRead?.Length, readResult.ErrorMessage));
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
readErrorCount++;
|
||||||
|
if (@this.LogMessage?.LogLevel <= TouchSocket.Core.LogLevel.Trace)
|
||||||
|
@this.LogMessage?.Trace(string.Format("{0} - Collection [{1} - {2}] failed - {3}", @this.DeviceName, variableSourceRead?.RegisterAddress, variableSourceRead?.Length, readResult.ErrorMessage));
|
||||||
|
|
||||||
|
//if (LogMessage?.LogLevel <= TouchSocket.Core.LogLevel.Trace)
|
||||||
|
// LogMessage?.Trace(string.Format("{0} - Collecting [{1} - {2}]", DeviceName, variableSourceRead?.RegisterAddress, variableSourceRead?.Length));
|
||||||
|
var readTask1 = @this.ReadSourceAsync(variableSourceRead, allToken);
|
||||||
|
if (!readTask1.IsCompleted)
|
||||||
|
{
|
||||||
|
readResult = await readTask1.ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
readResult = readTask1.Result;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
variableSourceRead.LastErrorMessage = readResult.ErrorMessage;
|
if (readResult.IsSuccess)
|
||||||
CurrentDevice.SetDeviceStatus(TimerX.Now, null, readResult.ErrorMessage);
|
|
||||||
var time = DateTime.Now;
|
|
||||||
foreach (var item in variableSourceRead.VariableRuntimes)
|
|
||||||
{
|
{
|
||||||
item.SetValue(null, time, isOnline: false);
|
// 读取成功时记录日志并增加成功计数器
|
||||||
|
if (@this.LogMessage?.LogLevel <= TouchSocket.Core.LogLevel.Trace)
|
||||||
|
@this.LogMessage?.Trace(string.Format("{0} - Collection [{1} - {2}] data succeeded {3}", @this.DeviceName, variableSourceRead?.RegisterAddress, variableSourceRead?.Length, readResult.Content.Span.ToHexString(' ')));
|
||||||
|
@this.CurrentDevice.SetDeviceStatus(TimerX.Now, null);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (cancellationToken.IsCancellationRequested)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (readToken.IsCancellationRequested)
|
||||||
|
{
|
||||||
|
await @this.ReadVariableSource(state, cancellationToken).ConfigureAwait(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 读取失败时记录日志并增加失败计数器,更新错误信息并清除变量状态
|
||||||
|
if (variableSourceRead.LastErrorMessage != readResult.ErrorMessage)
|
||||||
|
{
|
||||||
|
if (!cancellationToken.IsCancellationRequested)
|
||||||
|
@this.LogMessage?.LogWarning(readResult.Exception, string.Format(AppResource.CollectFail, @this.DeviceName, variableSourceRead?.RegisterAddress, variableSourceRead?.Length, readResult.ErrorMessage));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (!cancellationToken.IsCancellationRequested)
|
||||||
|
{
|
||||||
|
if (@this.LogMessage?.LogLevel <= TouchSocket.Core.LogLevel.Trace)
|
||||||
|
@this.LogMessage?.Trace(string.Format("{0} - Collection [{1} - {2}] data failed - {3}", @this.DeviceName, variableSourceRead?.RegisterAddress, variableSourceRead?.Length, readResult.ErrorMessage));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
variableSourceRead.LastErrorMessage = readResult.ErrorMessage;
|
||||||
|
@this.CurrentDevice.SetDeviceStatus(TimerX.Now, null, readResult.ErrorMessage);
|
||||||
|
var time = DateTime.Now;
|
||||||
|
foreach (var item in variableSourceRead.VariableRuntimes)
|
||||||
|
{
|
||||||
|
item.SetValue(null, time, isOnline: false);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// private ValueTask ReadVariableSource(object? state, CancellationToken cancellationToken)
|
||||||
|
// {
|
||||||
|
// var enumerator = new ReadVariableSourceEnumerator(this, state, cancellationToken);
|
||||||
|
// return enumerator.MoveNextAsync();
|
||||||
|
// }
|
||||||
|
|
||||||
|
// private struct ReadVariableSourceEnumerator
|
||||||
|
// {
|
||||||
|
// private readonly CollectBase _owner;
|
||||||
|
// private readonly object? _state;
|
||||||
|
// private readonly CancellationToken _cancellationToken;
|
||||||
|
|
||||||
|
// private VariableSourceRead _variableSourceRead;
|
||||||
|
// private CancellationToken _readToken;
|
||||||
|
// private CancellationToken _allToken;
|
||||||
|
// private OperResult<ReadOnlyMemory<byte>> _readResult;
|
||||||
|
// private int _readErrorCount;
|
||||||
|
// private ValueTask<CancellationToken> _readerLockTask;
|
||||||
|
// private ValueTask<OperResult<ReadOnlyMemory<byte>>> _readTask;
|
||||||
|
// private int _step;
|
||||||
|
|
||||||
|
// public ReadVariableSourceEnumerator(CollectBase owner, object? state, CancellationToken cancellationToken)
|
||||||
|
// {
|
||||||
|
// _owner = owner;
|
||||||
|
// _state = state;
|
||||||
|
// _cancellationToken = cancellationToken;
|
||||||
|
|
||||||
|
// _variableSourceRead = default!;
|
||||||
|
// _readToken = default;
|
||||||
|
// _allToken = default;
|
||||||
|
// _readResult = default;
|
||||||
|
// _readErrorCount = 0;
|
||||||
|
// _readerLockTask = default;
|
||||||
|
// _readTask = default;
|
||||||
|
// _step = 0;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// public ValueTask MoveNextAsync()
|
||||||
|
// {
|
||||||
|
// switch (_step)
|
||||||
|
// {
|
||||||
|
// case 0:
|
||||||
|
// if (_state is not VariableSourceRead vsr) return default;
|
||||||
|
// _variableSourceRead = vsr;
|
||||||
|
|
||||||
|
// if (_owner.Pause) return default;
|
||||||
|
// if (_cancellationToken.IsCancellationRequested) return default;
|
||||||
|
|
||||||
|
//#pragma warning disable CA2012 // 正确使用 ValueTask
|
||||||
|
// _readerLockTask = _owner.ReadWriteLock.ReaderLockAsync(_cancellationToken);
|
||||||
|
//#pragma warning restore CA2012 // 正确使用 ValueTask
|
||||||
|
// if (!_readerLockTask.IsCompleted)
|
||||||
|
// {
|
||||||
|
// _step = 1;
|
||||||
|
// return AwaitReaderLock();
|
||||||
|
// }
|
||||||
|
// _readToken = _readerLockTask.Result;
|
||||||
|
// goto case 2;
|
||||||
|
|
||||||
|
// case 1:
|
||||||
|
// _readToken = _readerLockTask.Result;
|
||||||
|
// goto case 2;
|
||||||
|
|
||||||
|
// case 2:
|
||||||
|
// if (_readToken.IsCancellationRequested)
|
||||||
|
// {
|
||||||
|
// return _owner.ReadVariableSource(_state, _cancellationToken);
|
||||||
|
// }
|
||||||
|
|
||||||
|
// var allTokenSource = _owner._linkedCtsCache.GetLinkedTokenSource(_cancellationToken, _readToken);
|
||||||
|
// _allToken = allTokenSource.Token;
|
||||||
|
|
||||||
|
//#pragma warning disable CA2012 // 正确使用 ValueTask
|
||||||
|
// _readTask = _owner.ReadSourceAsync(_variableSourceRead, _allToken);
|
||||||
|
//#pragma warning restore CA2012 // 正确使用 ValueTask
|
||||||
|
// if (!_readTask.IsCompleted)
|
||||||
|
// {
|
||||||
|
// _step = 3;
|
||||||
|
// return AwaitRead();
|
||||||
|
// }
|
||||||
|
// _readResult = _readTask.Result;
|
||||||
|
// goto case 4;
|
||||||
|
|
||||||
|
// case 3:
|
||||||
|
// _readResult = _readTask.Result;
|
||||||
|
// goto case 4;
|
||||||
|
|
||||||
|
// case 4:
|
||||||
|
// while (!_readResult.IsSuccess && _readErrorCount < _owner.CollectProperties.RetryCount)
|
||||||
|
// {
|
||||||
|
// if (_owner.Pause) return default;
|
||||||
|
// if (_cancellationToken.IsCancellationRequested) return default;
|
||||||
|
|
||||||
|
// if (_readToken.IsCancellationRequested)
|
||||||
|
// {
|
||||||
|
// return _owner.ReadVariableSource(_state, _cancellationToken);
|
||||||
|
// }
|
||||||
|
|
||||||
|
// _readErrorCount++;
|
||||||
|
// if (_owner.LogMessage?.LogLevel <= TouchSocket.Core.LogLevel.Trace)
|
||||||
|
// _owner.LogMessage?.Trace(string.Format("{0} - Collection [{1} - {2}] failed - {3}",
|
||||||
|
// _owner.DeviceName, _variableSourceRead?.RegisterAddress, _variableSourceRead?.Length, _readResult.ErrorMessage));
|
||||||
|
|
||||||
|
//#pragma warning disable CA2012 // 正确使用 ValueTask
|
||||||
|
// _readTask = _owner.ReadSourceAsync(_variableSourceRead, _allToken);
|
||||||
|
//#pragma warning restore CA2012 // 正确使用 ValueTask
|
||||||
|
// if (!_readTask.IsCompleted)
|
||||||
|
// {
|
||||||
|
// _step = 5;
|
||||||
|
// return AwaitReadRetry();
|
||||||
|
// }
|
||||||
|
// _readResult = _readTask.Result;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// goto case 6;
|
||||||
|
|
||||||
|
// case 5:
|
||||||
|
// _readResult = _readTask.Result;
|
||||||
|
// _step = 4;
|
||||||
|
// return MoveNextAsync();
|
||||||
|
|
||||||
|
// case 6:
|
||||||
|
// if (_readResult.IsSuccess)
|
||||||
|
// {
|
||||||
|
// if (_owner.LogMessage?.LogLevel <= TouchSocket.Core.LogLevel.Trace)
|
||||||
|
// _owner.LogMessage?.Trace(string.Format("{0} - Collection [{1} - {2}] data succeeded {3}",
|
||||||
|
// _owner.DeviceName, _variableSourceRead?.RegisterAddress, _variableSourceRead?.Length, _readResult.Content.Span.ToHexString(' ')));
|
||||||
|
|
||||||
|
// _owner.CurrentDevice.SetDeviceStatus(TimerX.Now, null);
|
||||||
|
// }
|
||||||
|
// else
|
||||||
|
// {
|
||||||
|
// if (_cancellationToken.IsCancellationRequested) return default;
|
||||||
|
// if (_readToken.IsCancellationRequested)
|
||||||
|
// {
|
||||||
|
// return _owner.ReadVariableSource(_state, _cancellationToken);
|
||||||
|
// }
|
||||||
|
|
||||||
|
// if (_variableSourceRead.LastErrorMessage != _readResult.ErrorMessage)
|
||||||
|
// {
|
||||||
|
// if (!_cancellationToken.IsCancellationRequested)
|
||||||
|
// _owner.LogMessage?.LogWarning(_readResult.Exception, string.Format(AppResource.CollectFail, _owner.DeviceName,
|
||||||
|
// _variableSourceRead?.RegisterAddress, _variableSourceRead?.Length, _readResult.ErrorMessage));
|
||||||
|
// }
|
||||||
|
// else
|
||||||
|
// {
|
||||||
|
// if (!_cancellationToken.IsCancellationRequested && _owner.LogMessage?.LogLevel <= TouchSocket.Core.LogLevel.Trace)
|
||||||
|
// _owner.LogMessage?.Trace(string.Format("{0} - Collection [{1} - {2}] data failed - {3}",
|
||||||
|
// _owner.DeviceName, _variableSourceRead?.RegisterAddress, _variableSourceRead?.Length, _readResult.ErrorMessage));
|
||||||
|
// }
|
||||||
|
|
||||||
|
// _variableSourceRead.LastErrorMessage = _readResult.ErrorMessage;
|
||||||
|
// _owner.CurrentDevice.SetDeviceStatus(TimerX.Now, null, _readResult.ErrorMessage);
|
||||||
|
// var time = DateTime.Now;
|
||||||
|
// foreach (var item in _variableSourceRead.VariableRuntimes)
|
||||||
|
// {
|
||||||
|
// item.SetValue(null, time, isOnline: false);
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// break;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// return default;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// private async ValueTask AwaitReaderLock()
|
||||||
|
// {
|
||||||
|
// await _readerLockTask.ConfigureAwait(false);
|
||||||
|
// _step = 1;
|
||||||
|
// await MoveNextAsync().ConfigureAwait(false);
|
||||||
|
// }
|
||||||
|
|
||||||
|
// private async ValueTask AwaitRead()
|
||||||
|
// {
|
||||||
|
// await _readTask.ConfigureAwait(false);
|
||||||
|
// _step = 3;
|
||||||
|
// await MoveNextAsync().ConfigureAwait(false);
|
||||||
|
// }
|
||||||
|
|
||||||
|
// private async ValueTask AwaitReadRetry()
|
||||||
|
// {
|
||||||
|
// await _readTask.ConfigureAwait(false);
|
||||||
|
// _step = 5;
|
||||||
|
// await MoveNextAsync().ConfigureAwait(false);
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
|
|
||||||
protected virtual Task TestOnline(object? state, CancellationToken cancellationToken)
|
protected virtual ValueTask TestOnline(object? state, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
return Task.CompletedTask;
|
return ValueTask.CompletedTask;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void ScriptVariableRun(object? state, CancellationToken cancellationToken)
|
protected void ScriptVariableRun(object? state, CancellationToken cancellationToken)
|
||||||
@@ -533,38 +735,43 @@ public abstract partial class CollectBase : DriverBase
|
|||||||
{
|
{
|
||||||
throw new NotImplementedException();
|
throw new NotImplementedException();
|
||||||
}
|
}
|
||||||
protected async Task Check(Dictionary<VariableRuntime, JToken> writeInfoLists, NonBlockingDictionary<string, OperResult> operResults, CancellationToken cancellationToken)
|
protected Task Check(Dictionary<VariableRuntime, JToken> writeInfoLists, NonBlockingDictionary<string, OperResult> operResults, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
if (VariableSourceReadsEnable)
|
return Check(this, writeInfoLists, operResults, cancellationToken);
|
||||||
|
|
||||||
|
static async PooledTask Check(CollectBase @this, Dictionary<VariableRuntime, JToken> writeInfoLists, NonBlockingDictionary<string, OperResult> operResults, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
// 如果成功,每个变量都读取一次最新值,再次比较写入值
|
if (@this.VariableSourceReadsEnable)
|
||||||
var successfulWriteNames = operResults.Where(a => a.Value.IsSuccess).Select(a => a.Key).ToHashSet();
|
|
||||||
|
|
||||||
var groups = writeInfoLists.Select(a => a.Key).Where(a => a.RpcWriteCheck && a.ProtectType != ProtectTypeEnum.WriteOnly && successfulWriteNames.Contains(a.Name) && a.VariableSource != null).GroupBy(a => a.VariableSource as VariableSourceRead).Where(a => a.Key != null).ToArray();
|
|
||||||
|
|
||||||
await groups.ParallelForEachAsync(async (varRead, token) =>
|
|
||||||
{
|
{
|
||||||
var result = await ReadSourceAsync(varRead.Key, token).ConfigureAwait(false);
|
// 如果成功,每个变量都读取一次最新值,再次比较写入值
|
||||||
if (result.IsSuccess)
|
var successfulWriteNames = operResults.Where(a => a.Value.IsSuccess).Select(a => a.Key).ToHashSet();
|
||||||
|
|
||||||
|
var groups = writeInfoLists.Select(a => a.Key).Where(a => a.RpcWriteCheck && a.ProtectType != ProtectTypeEnum.WriteOnly && successfulWriteNames.Contains(a.Name) && a.VariableSource != null).GroupBy(a => a.VariableSource as VariableSourceRead).Where(a => a.Key != null).ToArray();
|
||||||
|
|
||||||
|
await groups.ParallelForEachAsync(async (varRead, token) =>
|
||||||
{
|
{
|
||||||
foreach (var item in varRead)
|
var result = await @this.ReadSourceAsync(varRead.Key, token).ConfigureAwait(false);
|
||||||
|
if (result.IsSuccess)
|
||||||
{
|
{
|
||||||
if (!item.Value.Equals(writeInfoLists[item].ToObject(item.Value?.GetType())))
|
foreach (var item in varRead)
|
||||||
{
|
{
|
||||||
// 如果写入值与读取值不同,则更新操作结果为失败
|
if (!item.Value.Equals(writeInfoLists[item].ToObject(item.Value?.GetType())))
|
||||||
operResults[item.Name] = new OperResult($"The write value is inconsistent with the read value, Write value: {writeInfoLists[item].ToObject(item.Value?.GetType())}, read value: {item.Value}");
|
{
|
||||||
|
// 如果写入值与读取值不同,则更新操作结果为失败
|
||||||
|
operResults[item.Name] = new OperResult($"The write value is inconsistent with the read value, Write value: {writeInfoLists[item].ToObject(item.Value?.GetType())}, read value: {item.Value}");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
else
|
||||||
else
|
|
||||||
{
|
|
||||||
foreach (var item in varRead)
|
|
||||||
{
|
{
|
||||||
// 如果写入值与读取值不同,则更新操作结果为失败
|
foreach (var item in varRead)
|
||||||
operResults[item.Name] = new OperResult($"Reading and rechecking resulted in an error: {result.ErrorMessage}", result.Exception);
|
{
|
||||||
|
// 如果写入值与读取值不同,则更新操作结果为失败
|
||||||
|
operResults[item.Name] = new OperResult($"Reading and rechecking resulted in an error: {result.ErrorMessage}", result.Exception);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}, cancellationToken).ConfigureAwait(false);
|
||||||
}, cancellationToken).ConfigureAwait(false);
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -576,66 +783,71 @@ public abstract partial class CollectBase : DriverBase
|
|||||||
/// <param name="writeInfoLists">要写入的变量及其对应的数据</param>
|
/// <param name="writeInfoLists">要写入的变量及其对应的数据</param>
|
||||||
/// <param name="cancellationToken">取消操作的通知</param>
|
/// <param name="cancellationToken">取消操作的通知</param>
|
||||||
/// <returns>写入操作的结果字典</returns>
|
/// <returns>写入操作的结果字典</returns>
|
||||||
public async ValueTask<Dictionary<string, Dictionary<string, IOperResult>>> InvokeMethodAsync(Dictionary<VariableRuntime, JToken> writeInfoLists, CancellationToken cancellationToken)
|
public ValueTask<Dictionary<string, Dictionary<string, IOperResult>>> InvokeMethodAsync(Dictionary<VariableRuntime, JToken> writeInfoLists, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
// 初始化结果字典
|
return InvokeMethodAsync(this, writeInfoLists, cancellationToken);
|
||||||
Dictionary<string, OperResult<object>> results = new Dictionary<string, OperResult<object>>();
|
|
||||||
|
|
||||||
// 遍历写入信息列表
|
static async PooledValueTask<Dictionary<string, Dictionary<string, IOperResult>>> InvokeMethodAsync(CollectBase @this, Dictionary<VariableRuntime, JToken> writeInfoLists, CancellationToken cancellationToken)
|
||||||
foreach (var (deviceVariable, jToken) in writeInfoLists)
|
|
||||||
{
|
{
|
||||||
// 检查是否有写入表达式
|
// 初始化结果字典
|
||||||
if (!string.IsNullOrEmpty(deviceVariable.WriteExpressions))
|
Dictionary<string, OperResult<object>> results = new Dictionary<string, OperResult<object>>();
|
||||||
|
|
||||||
|
// 遍历写入信息列表
|
||||||
|
foreach (var (deviceVariable, jToken) in writeInfoLists)
|
||||||
|
{
|
||||||
|
// 检查是否有写入表达式
|
||||||
|
if (!string.IsNullOrEmpty(deviceVariable.WriteExpressions))
|
||||||
|
{
|
||||||
|
// 提取原始数据
|
||||||
|
object rawdata = jToken.GetObjectFromJToken();
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// 根据写入表达式转换数据
|
||||||
|
object data = deviceVariable.WriteExpressions.GetExpressionsResult(rawdata, @this.LogMessage);
|
||||||
|
// 将转换后的数据重新赋值给写入信息列表
|
||||||
|
writeInfoLists[deviceVariable] = JTokenUtil.GetJTokenFromObj(data);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
// 如果转换失败,则记录错误信息
|
||||||
|
results.Add(deviceVariable.Name, new OperResult<object>(string.Format(AppResource.WriteExpressionsError, deviceVariable.Name, deviceVariable.WriteExpressions, ex.Message), ex));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
NonBlockingDictionary<string, OperResult<object>> operResults = new();
|
||||||
|
|
||||||
|
|
||||||
|
using var writeLock = await @this.ReadWriteLock.WriterLockAsync(cancellationToken).ConfigureAwait(false);
|
||||||
|
var list = writeInfoLists
|
||||||
|
.Where(a => !results.Any(b => b.Key == a.Key.Name))
|
||||||
|
.ToArray();
|
||||||
|
// 使用并发方式遍历写入信息列表,并进行异步写入操作
|
||||||
|
await list.ParallelForEachAsync(async (writeInfo, cancellationToken) =>
|
||||||
{
|
{
|
||||||
// 提取原始数据
|
|
||||||
object rawdata = jToken.GetObjectFromJToken();
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
// 根据写入表达式转换数据
|
// 调用协议的写入方法,将写入信息中的数据写入到对应的寄存器地址,并获取操作结果
|
||||||
object data = deviceVariable.WriteExpressions.GetExpressionsResult(rawdata, LogMessage);
|
var result = await @this.InvokeMethodAsync(writeInfo.Key.VariableMethod, writeInfo.Value?.ToString(), false, cancellationToken).ConfigureAwait(false);
|
||||||
// 将转换后的数据重新赋值给写入信息列表
|
|
||||||
writeInfoLists[deviceVariable] = JTokenUtil.GetJTokenFromObj(data);
|
// 将操作结果添加到结果字典中,使用变量名称作为键
|
||||||
|
operResults.TryAdd(writeInfo.Key.Name, result);
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
// 如果转换失败,则记录错误信息
|
operResults.TryAdd(writeInfo.Key.Name, new(ex));
|
||||||
results.Add(deviceVariable.Name, new OperResult<object>(string.Format(AppResource.WriteExpressionsError, deviceVariable.Name, deviceVariable.WriteExpressions, ex.Message), ex));
|
|
||||||
}
|
}
|
||||||
}
|
}, @this.CollectProperties.MaxConcurrentCount, cancellationToken).ConfigureAwait(false);
|
||||||
}
|
|
||||||
|
|
||||||
NonBlockingDictionary<string, OperResult<object>> operResults = new();
|
// 将转换失败的变量和写入成功的变量的操作结果合并到结果字典中
|
||||||
|
return new Dictionary<string, Dictionary<string, IOperResult>>()
|
||||||
|
|
||||||
using var writeLock = await ReadWriteLock.WriterLockAsync(cancellationToken).ConfigureAwait(false);
|
|
||||||
var list = writeInfoLists
|
|
||||||
.Where(a => !results.Any(b => b.Key == a.Key.Name))
|
|
||||||
.ToArray();
|
|
||||||
// 使用并发方式遍历写入信息列表,并进行异步写入操作
|
|
||||||
await list.ParallelForEachAsync(async (writeInfo, cancellationToken) =>
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
// 调用协议的写入方法,将写入信息中的数据写入到对应的寄存器地址,并获取操作结果
|
|
||||||
var result = await InvokeMethodAsync(writeInfo.Key.VariableMethod, writeInfo.Value?.ToString(), false, cancellationToken).ConfigureAwait(false);
|
|
||||||
|
|
||||||
// 将操作结果添加到结果字典中,使用变量名称作为键
|
|
||||||
operResults.TryAdd(writeInfo.Key.Name, result);
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
operResults.TryAdd(writeInfo.Key.Name, new(ex));
|
|
||||||
}
|
|
||||||
}, CollectProperties.MaxConcurrentCount, cancellationToken).ConfigureAwait(false);
|
|
||||||
|
|
||||||
// 将转换失败的变量和写入成功的变量的操作结果合并到结果字典中
|
|
||||||
return new Dictionary<string, Dictionary<string, IOperResult>>()
|
|
||||||
{
|
{
|
||||||
{
|
{
|
||||||
DeviceName ,
|
@this.DeviceName ,
|
||||||
results.Concat(operResults).ToDictionary(a => a.Key, a => (IOperResult)a.Value)
|
results.Concat(operResults).ToDictionary(a => a.Key, a => (IOperResult)a.Value)
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -644,58 +856,63 @@ public abstract partial class CollectBase : DriverBase
|
|||||||
/// <param name="writeInfoLists">要写入的变量及其对应的数据</param>
|
/// <param name="writeInfoLists">要写入的变量及其对应的数据</param>
|
||||||
/// <param name="cancellationToken">取消操作的通知</param>
|
/// <param name="cancellationToken">取消操作的通知</param>
|
||||||
/// <returns>写入操作的结果字典</returns>
|
/// <returns>写入操作的结果字典</returns>
|
||||||
public async ValueTask<Dictionary<string, Dictionary<string, IOperResult>>> InVokeWriteAsync(Dictionary<VariableRuntime, JToken> writeInfoLists, CancellationToken cancellationToken)
|
public ValueTask<Dictionary<string, Dictionary<string, IOperResult>>> InVokeWriteAsync(Dictionary<VariableRuntime, JToken> writeInfoLists, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
// 初始化结果字典
|
return InVokeWriteAsync(this, writeInfoLists, cancellationToken);
|
||||||
Dictionary<string, OperResult> results = new Dictionary<string, OperResult>();
|
|
||||||
|
|
||||||
// 遍历写入信息列表
|
static async PooledValueTask<Dictionary<string, Dictionary<string, IOperResult>>> InVokeWriteAsync(CollectBase @this, Dictionary<VariableRuntime, JToken> writeInfoLists, CancellationToken cancellationToken)
|
||||||
foreach (var (deviceVariable, jToken) in writeInfoLists)
|
|
||||||
{
|
{
|
||||||
// 检查是否有写入表达式
|
// 初始化结果字典
|
||||||
if (!string.IsNullOrEmpty(deviceVariable.WriteExpressions))
|
Dictionary<string, OperResult> results = new Dictionary<string, OperResult>();
|
||||||
|
|
||||||
|
// 遍历写入信息列表
|
||||||
|
foreach (var (deviceVariable, jToken) in writeInfoLists)
|
||||||
{
|
{
|
||||||
// 提取原始数据
|
// 检查是否有写入表达式
|
||||||
object rawdata = jToken.GetObjectFromJToken();
|
if (!string.IsNullOrEmpty(deviceVariable.WriteExpressions))
|
||||||
try
|
|
||||||
{
|
{
|
||||||
// 根据写入表达式转换数据
|
// 提取原始数据
|
||||||
object data = deviceVariable.WriteExpressions.GetExpressionsResult(rawdata, LogMessage);
|
object rawdata = jToken.GetObjectFromJToken();
|
||||||
// 将转换后的数据重新赋值给写入信息列表
|
try
|
||||||
writeInfoLists[deviceVariable] = JTokenUtil.GetJTokenFromObj(data);
|
{
|
||||||
}
|
// 根据写入表达式转换数据
|
||||||
catch (Exception ex)
|
object data = deviceVariable.WriteExpressions.GetExpressionsResult(rawdata, @this.LogMessage);
|
||||||
{
|
// 将转换后的数据重新赋值给写入信息列表
|
||||||
// 如果转换失败,则记录错误信息
|
writeInfoLists[deviceVariable] = JTokenUtil.GetJTokenFromObj(data);
|
||||||
results.Add(deviceVariable.Name, new OperResult(string.Format(AppResource.WriteExpressionsError, deviceVariable.Name, deviceVariable.WriteExpressions, ex.Message), ex));
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
// 如果转换失败,则记录错误信息
|
||||||
|
results.Add(deviceVariable.Name, new OperResult(string.Format(AppResource.WriteExpressionsError, deviceVariable.Name, deviceVariable.WriteExpressions, ex.Message), ex));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
var writePList = writeInfoLists.Where(a => !CurrentDevice.VariableScriptReads.Select(a => a.VariableRuntime).Any(b => a.Key.Name == b.Name));
|
var writePList = writeInfoLists.Where(a => !@this.CurrentDevice.VariableScriptReads.Select(a => a.VariableRuntime).Any(b => a.Key.Name == b.Name));
|
||||||
var writeSList = writeInfoLists.Where(a => CurrentDevice.VariableScriptReads.Select(a => a.VariableRuntime).Any(b => a.Key.Name == b.Name));
|
var writeSList = writeInfoLists.Where(a => @this.CurrentDevice.VariableScriptReads.Select(a => a.VariableRuntime).Any(b => a.Key.Name == b.Name));
|
||||||
|
|
||||||
DateTime now = DateTime.Now;
|
DateTime now = DateTime.Now;
|
||||||
foreach (var item in writeSList)
|
foreach (var item in writeSList)
|
||||||
{
|
{
|
||||||
results.TryAdd(item.Key.Name, item.Key.SetValue(item.Value, now));
|
results.TryAdd(item.Key.Name, item.Key.SetValue(item.Value, now));
|
||||||
}
|
}
|
||||||
|
|
||||||
// 过滤掉转换失败的变量,只保留写入成功的变量进行写入操作
|
// 过滤掉转换失败的变量,只保留写入成功的变量进行写入操作
|
||||||
var results1 = await WriteValuesAsync(writePList
|
var results1 = await @this.WriteValuesAsync(writePList
|
||||||
.Where(a => !results.Any(b => b.Key == a.Key.Name))
|
.Where(a => !results.Any(b => b.Key == a.Key.Name))
|
||||||
.ToDictionary(item => item.Key, item => item.Value),
|
.ToDictionary(item => item.Key, item => item.Value),
|
||||||
cancellationToken).ConfigureAwait(false);
|
cancellationToken).ConfigureAwait(false);
|
||||||
|
|
||||||
// 将转换失败的变量和写入成功的变量的操作结果合并到结果字典中
|
// 将转换失败的变量和写入成功的变量的操作结果合并到结果字典中
|
||||||
|
|
||||||
return new Dictionary<string, Dictionary<string, IOperResult>>()
|
return new Dictionary<string, Dictionary<string, IOperResult>>()
|
||||||
{
|
{
|
||||||
{
|
{
|
||||||
DeviceName ,
|
@this. DeviceName ,
|
||||||
results.Concat(results1).ToDictionary(a => a.Key, a => (IOperResult)a.Value)
|
results.Concat(results1).ToDictionary(a => a.Key, a => (IOperResult)a.Value)
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -706,56 +923,61 @@ public abstract partial class CollectBase : DriverBase
|
|||||||
/// <param name="isRead">指示是否为读取操作</param>
|
/// <param name="isRead">指示是否为读取操作</param>
|
||||||
/// <param name="cancellationToken">取消操作的通知</param>
|
/// <param name="cancellationToken">取消操作的通知</param>
|
||||||
/// <returns>操作结果,包含执行方法的结果</returns>
|
/// <returns>操作结果,包含执行方法的结果</returns>
|
||||||
protected virtual async ValueTask<OperResult<object>> InvokeMethodAsync(VariableMethod variableMethod, string? value = null, bool isRead = true, CancellationToken cancellationToken = default)
|
protected virtual ValueTask<OperResult<object>> InvokeMethodAsync(VariableMethod variableMethod, string? value = null, bool isRead = true, CancellationToken cancellationToken = default)
|
||||||
{
|
{
|
||||||
try
|
return InvokeMethodAsync(this, variableMethod, value, isRead, cancellationToken);
|
||||||
|
|
||||||
|
static async PooledValueTask<OperResult<object>> InvokeMethodAsync(CollectBase @this, VariableMethod variableMethod, string? value, bool isRead, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
// 初始化操作结果
|
try
|
||||||
OperResult<object> result = new OperResult<object>();
|
|
||||||
|
|
||||||
// 获取要执行的方法
|
|
||||||
var method = variableMethod.MethodInfo;
|
|
||||||
|
|
||||||
// 如果方法未找到,则返回错误结果
|
|
||||||
if (method == null)
|
|
||||||
{
|
{
|
||||||
result.OperCode = 999;
|
// 初始化操作结果
|
||||||
result.ErrorMessage = string.Format(AppResource.MethodNotNull, variableMethod.Variable.Name, variableMethod.Variable.OtherMethod);
|
OperResult<object> result = new OperResult<object>();
|
||||||
return result;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// 调用方法并获取结果
|
|
||||||
var data = await variableMethod.InvokeMethodAsync(this, value, cancellationToken).ConfigureAwait(false);
|
|
||||||
|
|
||||||
result = data.GetOperResult();
|
// 获取要执行的方法
|
||||||
|
var method = variableMethod.MethodInfo;
|
||||||
|
|
||||||
// 如果方法有返回值,并且是读取操作
|
// 如果方法未找到,则返回错误结果
|
||||||
if (method.HasReturn && isRead)
|
if (method == null)
|
||||||
{
|
{
|
||||||
var time = DateTime.Now;
|
result.OperCode = 999;
|
||||||
if (result.IsSuccess == true)
|
result.ErrorMessage = string.Format(AppResource.MethodNotNull, variableMethod.Variable.Name, variableMethod.Variable.OtherMethod);
|
||||||
{
|
return result;
|
||||||
// 将结果序列化并设置到变量中
|
}
|
||||||
var variableResult = variableMethod.Variable.SetValue(result.Content, time);
|
else
|
||||||
if (!variableResult.IsSuccess)
|
{
|
||||||
variableMethod.LastErrorMessage = result.ErrorMessage;
|
// 调用方法并获取结果
|
||||||
}
|
var data = await variableMethod.InvokeMethodAsync(@this, value, cancellationToken).ConfigureAwait(false);
|
||||||
else
|
|
||||||
{
|
result = data.GetOperResult();
|
||||||
// 如果读取操作失败,则将变量标记为离线
|
|
||||||
var variableResult = variableMethod.Variable.SetValue(null, time, isOnline: false);
|
// 如果方法有返回值,并且是读取操作
|
||||||
if (!variableResult.IsSuccess)
|
if (method.HasReturn && isRead)
|
||||||
variableMethod.LastErrorMessage = result.ErrorMessage;
|
{
|
||||||
}
|
var time = DateTime.Now;
|
||||||
|
if (result.IsSuccess == true)
|
||||||
|
{
|
||||||
|
// 将结果序列化并设置到变量中
|
||||||
|
var variableResult = variableMethod.Variable.SetValue(result.Content, time);
|
||||||
|
if (!variableResult.IsSuccess)
|
||||||
|
variableMethod.LastErrorMessage = result.ErrorMessage;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// 如果读取操作失败,则将变量标记为离线
|
||||||
|
var variableResult = variableMethod.Variable.SetValue(null, time, isOnline: false);
|
||||||
|
if (!variableResult.IsSuccess)
|
||||||
|
variableMethod.LastErrorMessage = result.ErrorMessage;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
return result;
|
|
||||||
}
|
}
|
||||||
}
|
catch (Exception ex)
|
||||||
catch (Exception ex)
|
{
|
||||||
{
|
// 捕获异常并返回错误结果
|
||||||
// 捕获异常并返回错误结果
|
return new OperResult<object>(ex);
|
||||||
return new OperResult<object>(ex);
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -12,6 +12,8 @@ using Microsoft.Extensions.Logging;
|
|||||||
|
|
||||||
using Newtonsoft.Json.Linq;
|
using Newtonsoft.Json.Linq;
|
||||||
|
|
||||||
|
using PooledAwait;
|
||||||
|
|
||||||
using System.Collections.Concurrent;
|
using System.Collections.Concurrent;
|
||||||
|
|
||||||
using ThingsGateway.Common.Extension;
|
using ThingsGateway.Common.Extension;
|
||||||
@@ -79,158 +81,284 @@ public abstract class CollectFoundationBase : CollectBase
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
protected override async Task TestOnline(object? state, CancellationToken cancellationToken)
|
protected override ValueTask TestOnline(object? state, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
if (FoundationDevice != null)
|
return TestOnline(this, cancellationToken);
|
||||||
|
|
||||||
|
|
||||||
|
static async PooledValueTask TestOnline(CollectFoundationBase @this, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
if (!FoundationDevice.OnLine)
|
if (@this.FoundationDevice != null)
|
||||||
{
|
{
|
||||||
if (!FoundationDevice.DisposedValue || FoundationDevice.Channel?.DisposedValue != false) return;
|
if (!@this.FoundationDevice.OnLine)
|
||||||
Exception exception = null;
|
|
||||||
try
|
|
||||||
{
|
{
|
||||||
if (!cancellationToken.IsCancellationRequested)
|
if (!@this.FoundationDevice.DisposedValue || @this.FoundationDevice.Channel?.DisposedValue != false) return;
|
||||||
|
Exception exception = null;
|
||||||
|
try
|
||||||
{
|
{
|
||||||
if (!FoundationDevice.DisposedValue || FoundationDevice.Channel?.DisposedValue != false) return;
|
if (!cancellationToken.IsCancellationRequested)
|
||||||
|
|
||||||
await FoundationDevice.ConnectAsync(cancellationToken).ConfigureAwait(false);
|
|
||||||
|
|
||||||
if (CurrentDevice.DeviceStatusChangeTime < TimerX.Now.AddMinutes(-1))
|
|
||||||
{
|
{
|
||||||
await Task.Delay(30000, cancellationToken).ConfigureAwait(false);
|
if (!@this.FoundationDevice.DisposedValue || @this.FoundationDevice.Channel?.DisposedValue != false) return;
|
||||||
|
|
||||||
|
await @this.FoundationDevice.ConnectAsync(cancellationToken).ConfigureAwait(false);
|
||||||
|
|
||||||
|
if (@this.CurrentDevice.DeviceStatusChangeTime < TimerX.Now.AddMinutes(-1))
|
||||||
|
{
|
||||||
|
await Task.Delay(30000, cancellationToken).ConfigureAwait(false);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
catch (OperationCanceledException)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
exception = ex;
|
||||||
|
}
|
||||||
|
if (cancellationToken.IsCancellationRequested)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (@this.FoundationDevice.OnLine == false && exception != null)
|
||||||
|
{
|
||||||
|
foreach (var item in @this.CurrentDevice.VariableSourceReads)
|
||||||
|
{
|
||||||
|
if (item.LastErrorMessage != exception.Message)
|
||||||
|
{
|
||||||
|
if (!cancellationToken.IsCancellationRequested)
|
||||||
|
@this.LogMessage?.LogWarning(exception, string.Format(AppResource.CollectFail, @this.DeviceName, item?.RegisterAddress, item?.Length, exception.Message));
|
||||||
|
}
|
||||||
|
item.LastErrorMessage = exception.Message;
|
||||||
|
@this.CurrentDevice.SetDeviceStatus(TimerX.Now, null, exception.Message);
|
||||||
|
var time = DateTime.Now;
|
||||||
|
item.VariableRuntimes.ForEach(a => a.SetValue(null, time, isOnline: false));
|
||||||
|
}
|
||||||
|
foreach (var item in @this.CurrentDevice.ReadVariableMethods)
|
||||||
|
{
|
||||||
|
if (item.LastErrorMessage != exception.Message)
|
||||||
|
{
|
||||||
|
if (!cancellationToken.IsCancellationRequested)
|
||||||
|
@this.LogMessage?.LogWarning(exception, string.Format(AppResource.MethodFail, @this.DeviceName, item.MethodInfo.Name, exception.Message));
|
||||||
|
}
|
||||||
|
item.LastErrorMessage = exception.Message;
|
||||||
|
@this.CurrentDevice.SetDeviceStatus(TimerX.Now, null, exception.Message);
|
||||||
|
var time = DateTime.Now;
|
||||||
|
item.Variable.SetValue(null, time, isOnline: false);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
catch (OperationCanceledException)
|
}
|
||||||
{
|
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
|
||||||
{
|
}
|
||||||
exception = ex;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
protected override ValueTask<OperResult<ReadOnlyMemory<byte>>> ReadSourceAsync(VariableSourceRead variableSourceRead, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
return ReadSourceAsync(this, variableSourceRead, cancellationToken);
|
||||||
|
|
||||||
|
|
||||||
|
static async PooledValueTask<OperResult<ReadOnlyMemory<byte>>> ReadSourceAsync(CollectFoundationBase @this, VariableSourceRead variableSourceRead, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
|
||||||
if (cancellationToken.IsCancellationRequested)
|
if (cancellationToken.IsCancellationRequested)
|
||||||
{
|
return new(new OperationCanceledException());
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (FoundationDevice.OnLine == false && exception != null)
|
|
||||||
{
|
|
||||||
foreach (var item in CurrentDevice.VariableSourceReads)
|
|
||||||
{
|
|
||||||
if (item.LastErrorMessage != exception.Message)
|
|
||||||
{
|
|
||||||
if (!cancellationToken.IsCancellationRequested)
|
|
||||||
LogMessage?.LogWarning(exception, string.Format(AppResource.CollectFail, DeviceName, item?.RegisterAddress, item?.Length, exception.Message));
|
|
||||||
}
|
|
||||||
item.LastErrorMessage = exception.Message;
|
|
||||||
CurrentDevice.SetDeviceStatus(TimerX.Now, null, exception.Message);
|
|
||||||
var time = DateTime.Now;
|
|
||||||
item.VariableRuntimes.ForEach(a => a.SetValue(null, time, isOnline: false));
|
|
||||||
}
|
|
||||||
foreach (var item in CurrentDevice.ReadVariableMethods)
|
|
||||||
{
|
|
||||||
if (item.LastErrorMessage != exception.Message)
|
|
||||||
{
|
|
||||||
if (!cancellationToken.IsCancellationRequested)
|
|
||||||
LogMessage?.LogWarning(exception, string.Format(AppResource.MethodFail, DeviceName, item.MethodInfo.Name, exception.Message));
|
|
||||||
}
|
|
||||||
item.LastErrorMessage = exception.Message;
|
|
||||||
CurrentDevice.SetDeviceStatus(TimerX.Now, null, exception.Message);
|
|
||||||
var time = DateTime.Now;
|
|
||||||
item.Variable.SetValue(null, time, isOnline: false);
|
|
||||||
}
|
|
||||||
|
|
||||||
return;
|
// 从协议读取数据
|
||||||
|
OperResult<ReadOnlyMemory<byte>> read = default;
|
||||||
|
var readTask = @this.FoundationDevice.ReadAsync(variableSourceRead.AddressObject, cancellationToken);
|
||||||
|
if (!readTask.IsCompleted)
|
||||||
|
{
|
||||||
|
read = await readTask.ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
read = readTask.Result;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果读取成功且有有效内容,则解析结构化内容
|
||||||
|
if (read.IsSuccess)
|
||||||
|
{
|
||||||
|
var prase = variableSourceRead.VariableRuntimes.PraseStructContent(@this.FoundationDevice, read.Content.Span, false);
|
||||||
|
return new OperResult<ReadOnlyMemory<byte>>(prase);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 返回读取结果
|
||||||
|
return read;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
// 捕获异常并返回失败结果
|
||||||
|
return new OperResult<ReadOnlyMemory<byte>>(ex);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
///// <summary>
|
||||||
/// 采集驱动读取,读取成功后直接赋值变量,失败不做处理,注意非通用设备需重写
|
///// 采集驱动读取,读取成功后直接赋值变量,失败不做处理,注意非通用设备需重写
|
||||||
/// </summary>
|
///// </summary>
|
||||||
protected override async ValueTask<OperResult<ReadOnlyMemory<byte>>> ReadSourceAsync(VariableSourceRead variableSourceRead, CancellationToken cancellationToken)
|
// protected override ValueTask<OperResult<ReadOnlyMemory<byte>>> ReadSourceAsync(VariableSourceRead variableSourceRead, CancellationToken cancellationToken)
|
||||||
{
|
// {
|
||||||
try
|
// if (cancellationToken.IsCancellationRequested)
|
||||||
{
|
// return new ValueTask<OperResult<ReadOnlyMemory<byte>>>( new OperResult<ReadOnlyMemory<byte>>(new OperationCanceledException()));
|
||||||
|
|
||||||
if (cancellationToken.IsCancellationRequested)
|
// // 值类型状态机
|
||||||
return new(new OperationCanceledException());
|
// var stateMachine = new ReadSourceStateMachine(this, variableSourceRead, cancellationToken);
|
||||||
|
// return stateMachine.MoveNextAsync();
|
||||||
|
// }
|
||||||
|
|
||||||
// 从协议读取数据
|
// private struct ReadSourceStateMachine
|
||||||
OperResult<ReadOnlyMemory<byte>> read = default;
|
// {
|
||||||
var readTask = FoundationDevice.ReadAsync(variableSourceRead.AddressObject, cancellationToken);
|
// private readonly VariableSourceRead _variableSourceRead;
|
||||||
if (!readTask.IsCompleted)
|
// private readonly CancellationToken _cancellationToken;
|
||||||
{
|
// private readonly CollectFoundationBase _owner;
|
||||||
read = await readTask.ConfigureAwait(false);
|
// private OperResult<ReadOnlyMemory<byte>> _result;
|
||||||
}
|
// private ValueTask<OperResult<ReadOnlyMemory<byte>>> _readTask;
|
||||||
else
|
|
||||||
{
|
|
||||||
read = readTask.Result;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 如果读取成功且有有效内容,则解析结构化内容
|
// public ReadSourceStateMachine(CollectFoundationBase owner, VariableSourceRead variableSourceRead, CancellationToken cancellationToken)
|
||||||
if (read.IsSuccess)
|
// {
|
||||||
{
|
// _owner = owner;
|
||||||
var prase = variableSourceRead.VariableRuntimes.PraseStructContent(FoundationDevice, read.Content.Span, false);
|
// _variableSourceRead = variableSourceRead;
|
||||||
return new OperResult<ReadOnlyMemory<byte>>(prase);
|
// _cancellationToken = cancellationToken;
|
||||||
}
|
// _result = default;
|
||||||
|
// State = 0;
|
||||||
|
// }
|
||||||
|
|
||||||
// 返回读取结果
|
// public int State { get; private set; }
|
||||||
return read;
|
|
||||||
}
|
// public ValueTask<OperResult<ReadOnlyMemory<byte>>> MoveNextAsync()
|
||||||
catch (Exception ex)
|
// {
|
||||||
{
|
// try
|
||||||
// 捕获异常并返回失败结果
|
// {
|
||||||
return new OperResult<ReadOnlyMemory<byte>>(ex);
|
// switch (State)
|
||||||
}
|
// {
|
||||||
}
|
// case 0:
|
||||||
|
// // 异步读取
|
||||||
|
// if (_cancellationToken.IsCancellationRequested)
|
||||||
|
// {
|
||||||
|
// _result = new OperResult<ReadOnlyMemory<byte>>(new OperationCanceledException());
|
||||||
|
// return new ValueTask<OperResult<ReadOnlyMemory<byte>>>(_result);
|
||||||
|
// }
|
||||||
|
|
||||||
|
//#pragma warning disable CA2012 // 正确使用 ValueTask
|
||||||
|
// _readTask = _owner.FoundationDevice.ReadAsync(_variableSourceRead.AddressObject, _cancellationToken);
|
||||||
|
//#pragma warning restore CA2012 // 正确使用 ValueTask
|
||||||
|
|
||||||
|
// // 检查是否任务已完成
|
||||||
|
// if (_readTask.IsCompleted)
|
||||||
|
// {
|
||||||
|
// _result = _readTask.Result;
|
||||||
|
// State = 1;
|
||||||
|
// return MoveNextAsync();
|
||||||
|
// }
|
||||||
|
|
||||||
|
// // 如果任务尚未完成,继续等待
|
||||||
|
// State = 2;
|
||||||
|
// return Awaited(_readTask);
|
||||||
|
|
||||||
|
// case 1:
|
||||||
|
// // 解析结构化内容
|
||||||
|
// if (_result.IsSuccess)
|
||||||
|
// {
|
||||||
|
// var parsedResult = _variableSourceRead.VariableRuntimes.PraseStructContent(_owner.FoundationDevice, _result.Content.Span, false);
|
||||||
|
// return new ValueTask<OperResult<ReadOnlyMemory<byte>>>(new OperResult<ReadOnlyMemory<byte>>(parsedResult));
|
||||||
|
// }
|
||||||
|
|
||||||
|
// return new ValueTask<OperResult<ReadOnlyMemory<byte>>>(_result);
|
||||||
|
|
||||||
|
// case 2:
|
||||||
|
// // 完成任务后,解析内容
|
||||||
|
// _result = _readTask.Result;
|
||||||
|
|
||||||
|
// if (_result.IsSuccess)
|
||||||
|
// {
|
||||||
|
// var parsedResult = _variableSourceRead.VariableRuntimes.PraseStructContent(_owner.FoundationDevice, _result.Content.Span, false);
|
||||||
|
// return new ValueTask<OperResult<ReadOnlyMemory<byte>>>(new OperResult<ReadOnlyMemory<byte>>(parsedResult));
|
||||||
|
// }
|
||||||
|
|
||||||
|
// return new ValueTask<OperResult<ReadOnlyMemory<byte>>>(_result);
|
||||||
|
|
||||||
|
// default:
|
||||||
|
// throw new InvalidOperationException("Unexpected state.");
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// catch (Exception ex)
|
||||||
|
// {
|
||||||
|
// return new ValueTask<OperResult<ReadOnlyMemory<byte>>>(new OperResult<ReadOnlyMemory<byte>>(ex));
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
// private async ValueTask<OperResult<ReadOnlyMemory<byte>>> Awaited(ValueTask<OperResult<ReadOnlyMemory<byte>>> vt)
|
||||||
|
// {
|
||||||
|
// try
|
||||||
|
// {
|
||||||
|
|
||||||
|
|
||||||
|
// await vt.ConfigureAwait(false);
|
||||||
|
// return await MoveNextAsync().ConfigureAwait(false);
|
||||||
|
// }
|
||||||
|
// catch (Exception ex)
|
||||||
|
// {
|
||||||
|
// return new OperResult<ReadOnlyMemory<byte>>(ex);
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 批量写入变量值,需返回变量名称/结果,注意非通用设备需重写
|
/// 批量写入变量值,需返回变量名称/结果,注意非通用设备需重写
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
protected override async ValueTask<Dictionary<string, OperResult>> WriteValuesAsync(Dictionary<VariableRuntime, JToken> writeInfoLists, CancellationToken cancellationToken)
|
protected override ValueTask<Dictionary<string, OperResult>> WriteValuesAsync(Dictionary<VariableRuntime, JToken> writeInfoLists, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
using var writeLock = await ReadWriteLock.WriterLockAsync(cancellationToken).ConfigureAwait(false);
|
return WriteValuesAsync(this, writeInfoLists, cancellationToken);
|
||||||
// 检查协议是否为空,如果为空则抛出异常
|
|
||||||
if (FoundationDevice == null)
|
|
||||||
throw new NotSupportedException();
|
|
||||||
|
|
||||||
// 创建用于存储操作结果的并发字典
|
static async PooledValueTask<Dictionary<string, OperResult>> WriteValuesAsync(CollectFoundationBase @this, Dictionary<VariableRuntime, JToken> writeInfoLists, CancellationToken cancellationToken)
|
||||||
NonBlockingDictionary<string, OperResult> operResults = new();
|
|
||||||
// 使用并发方式遍历写入信息列表,并进行异步写入操作
|
|
||||||
await writeInfoLists.ParallelForEachAsync(async (writeInfo, cancellationToken) =>
|
|
||||||
{
|
{
|
||||||
try
|
using var writeLock = await @this.ReadWriteLock.WriterLockAsync(cancellationToken).ConfigureAwait(false);
|
||||||
|
// 检查协议是否为空,如果为空则抛出异常
|
||||||
|
if (@this.FoundationDevice == null)
|
||||||
|
throw new NotSupportedException();
|
||||||
|
|
||||||
|
// 创建用于存储操作结果的并发字典
|
||||||
|
NonBlockingDictionary<string, OperResult> operResults = new();
|
||||||
|
// 使用并发方式遍历写入信息列表,并进行异步写入操作
|
||||||
|
await writeInfoLists.ParallelForEachAsync(async (writeInfo, cancellationToken) =>
|
||||||
{
|
{
|
||||||
|
try
|
||||||
// 调用协议的写入方法,将写入信息中的数据写入到对应的寄存器地址,并获取操作结果
|
|
||||||
var result = await FoundationDevice.WriteJTokenAsync(writeInfo.Key.RegisterAddress, writeInfo.Value, writeInfo.Key.DataType, cancellationToken).ConfigureAwait(false);
|
|
||||||
|
|
||||||
if (result.IsSuccess)
|
|
||||||
{
|
{
|
||||||
if (LogMessage?.LogLevel <= TouchSocket.Core.LogLevel.Debug)
|
|
||||||
LogMessage?.Debug(string.Format("{0} - Write [{1} - {2} - {3}] data succeeded", DeviceName, writeInfo.Key.RegisterAddress, writeInfo.Value, writeInfo.Key.DataType));
|
// 调用协议的写入方法,将写入信息中的数据写入到对应的寄存器地址,并获取操作结果
|
||||||
|
var result = await @this.FoundationDevice.WriteJTokenAsync(writeInfo.Key.RegisterAddress, writeInfo.Value, writeInfo.Key.DataType, cancellationToken).ConfigureAwait(false);
|
||||||
|
|
||||||
|
if (result.IsSuccess)
|
||||||
|
{
|
||||||
|
if (@this.LogMessage?.LogLevel <= TouchSocket.Core.LogLevel.Debug)
|
||||||
|
@this.LogMessage?.Debug(string.Format("{0} - Write [{1} - {2} - {3}] data succeeded", @this.DeviceName, writeInfo.Key.RegisterAddress, writeInfo.Value, writeInfo.Key.DataType));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
@this.LogMessage?.Warning(string.Format("{0} - Write [{1} - {2} - {3}] data failed {4}", @this.DeviceName, writeInfo.Key.RegisterAddress, writeInfo.Value, writeInfo.Key.DataType, result.ToString()));
|
||||||
|
}
|
||||||
|
// 将操作结果添加到结果字典中,使用变量名称作为键
|
||||||
|
operResults.TryAdd(writeInfo.Key.Name, result);
|
||||||
}
|
}
|
||||||
else
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
LogMessage?.Warning(string.Format("{0} - Write [{1} - {2} - {3}] data failed {4}", DeviceName, writeInfo.Key.RegisterAddress, writeInfo.Value, writeInfo.Key.DataType, result.ToString()));
|
operResults.TryAdd(writeInfo.Key.Name, new(ex));
|
||||||
}
|
}
|
||||||
// 将操作结果添加到结果字典中,使用变量名称作为键
|
}, @this.CollectProperties.MaxConcurrentCount, cancellationToken).ConfigureAwait(false);
|
||||||
operResults.TryAdd(writeInfo.Key.Name, result);
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
operResults.TryAdd(writeInfo.Key.Name, new(ex));
|
|
||||||
}
|
|
||||||
}, CollectProperties.MaxConcurrentCount, cancellationToken).ConfigureAwait(false);
|
|
||||||
|
|
||||||
await Check(writeInfoLists, operResults, cancellationToken).ConfigureAwait(false);
|
await @this.Check(writeInfoLists, operResults, cancellationToken).ConfigureAwait(false);
|
||||||
|
|
||||||
// 返回包含操作结果的字典
|
// 返回包含操作结果的字典
|
||||||
return new Dictionary<string, OperResult>(operResults);
|
return new Dictionary<string, OperResult>(operResults);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ namespace ThingsGateway.Gateway.Application;
|
|||||||
[SugarIndex("index_device", nameof(Variable.DeviceId), OrderByType.Asc)]
|
[SugarIndex("index_device", nameof(Variable.DeviceId), OrderByType.Asc)]
|
||||||
[SugarIndex("unique_deviceid_variable_name", nameof(Variable.Name), OrderByType.Asc, nameof(Variable.DeviceId), OrderByType.Asc, true)]
|
[SugarIndex("unique_deviceid_variable_name", nameof(Variable.Name), OrderByType.Asc, nameof(Variable.DeviceId), OrderByType.Asc, true)]
|
||||||
#endif
|
#endif
|
||||||
public class Variable : BaseDataEntity, IValidatableObject
|
public class Variable : PrimaryKeyEntity, IValidatableObject
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 主键Id
|
/// 主键Id
|
||||||
|
|||||||
@@ -10,9 +10,12 @@
|
|||||||
|
|
||||||
using BootstrapBlazor.Components;
|
using BootstrapBlazor.Components;
|
||||||
|
|
||||||
|
using PooledAwait;
|
||||||
|
|
||||||
using System.Collections.Concurrent;
|
using System.Collections.Concurrent;
|
||||||
|
|
||||||
using ThingsGateway.Extension.Generic;
|
using ThingsGateway.Extension.Generic;
|
||||||
|
using ThingsGateway.NewLife.DictionaryExtensions;
|
||||||
|
|
||||||
namespace ThingsGateway.Gateway.Application;
|
namespace ThingsGateway.Gateway.Application;
|
||||||
|
|
||||||
@@ -89,38 +92,110 @@ public static class GlobalData
|
|||||||
|
|
||||||
public static event PluginEventHandler? PluginEventHandler;
|
public static event PluginEventHandler? PluginEventHandler;
|
||||||
|
|
||||||
public static async Task<IEnumerable<ChannelRuntime>> GetCurrentUserChannels()
|
public static Task<IEnumerable<ChannelRuntime>> GetCurrentUserChannels()
|
||||||
{
|
{
|
||||||
var dataScope = await GlobalData.SysUserService.GetCurrentUserDataScopeAsync().ConfigureAwait(false);
|
return GetCurrentUserChannels();
|
||||||
return ReadOnlyIdChannels.WhereIf(dataScope != null && dataScope?.Count > 0, u => dataScope.Contains(u.Value.CreateOrgId))//在指定机构列表查询
|
|
||||||
.WhereIf(dataScope?.Count == 0, u => u.Value.CreateUserId == UserManager.UserId).Select(a => a.Value);
|
static async PooledTask<IEnumerable<ChannelRuntime>> GetCurrentUserChannels()
|
||||||
|
{
|
||||||
|
var dataScope = await GlobalData.SysUserService.GetCurrentUserDataScopeAsync().ConfigureAwait(false);
|
||||||
|
return ReadOnlyIdChannels.WhereIf(dataScope != null && dataScope?.Count > 0, u => dataScope.Contains(u.Value.CreateOrgId))//在指定机构列表查询
|
||||||
|
.WhereIf(dataScope?.Count == 0, u => u.Value.CreateUserId == UserManager.UserId).Select(a => a.Value);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
public static async Task<IEnumerable<DeviceRuntime>> GetCurrentUserDevices()
|
public static Task<IEnumerable<DeviceRuntime>> GetCurrentUserDevices()
|
||||||
{
|
{
|
||||||
var dataScope = await GlobalData.SysUserService.GetCurrentUserDataScopeAsync().ConfigureAwait(false);
|
return GetCurrentUserDevices();
|
||||||
return ReadOnlyIdDevices.WhereIf(dataScope != null && dataScope?.Count > 0, u => dataScope.Contains(u.Value.CreateOrgId))//在指定机构列表查询
|
|
||||||
.WhereIf(dataScope?.Count == 0, u => u.Value.CreateUserId == UserManager.UserId).Select(a => a.Value);
|
static async PooledTask<IEnumerable<DeviceRuntime>> GetCurrentUserDevices()
|
||||||
|
{
|
||||||
|
var dataScope = await GlobalData.SysUserService.GetCurrentUserDataScopeAsync().ConfigureAwait(false);
|
||||||
|
return IdDevices.WhereIf(dataScope != null && dataScope?.Count > 0, u => dataScope.Contains(u.Value.CreateOrgId))//在指定机构列表查询
|
||||||
|
.WhereIf(dataScope?.Count == 0, u => u.Value.CreateUserId == UserManager.UserId).Select(a => a.Value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public static IEnumerable<long> GetCurrentUserDeviceIds(HashSet<long> dataScope)
|
||||||
|
{
|
||||||
|
return IdDevices.WhereIf(dataScope != null && dataScope?.Count > 0, u => dataScope.Contains(u.Value.CreateOrgId))//在指定机构列表查询
|
||||||
|
.WhereIf(dataScope?.Count == 0, u => u.Value.CreateUserId == UserManager.UserId).Select(a => a.Key);
|
||||||
|
}
|
||||||
|
public static Task<IEnumerable<VariableRuntime>> GetCurrentUserIdVariables()
|
||||||
|
{
|
||||||
|
return GetCurrentUserIdVariables();
|
||||||
|
|
||||||
|
static async PooledTask<IEnumerable<VariableRuntime>> GetCurrentUserIdVariables()
|
||||||
|
{
|
||||||
|
var dataScope = await GlobalData.SysUserService.GetCurrentUserDataScopeAsync().ConfigureAwait(false);
|
||||||
|
|
||||||
|
return IdVariables.Where(a => a.Value.IsInternalMemoryVariable == false)
|
||||||
|
.WhereIf(dataScope != null && dataScope?.Count > 0, u => dataScope.Contains(u.Value.DeviceRuntime.CreateOrgId))//在指定机构列表查询
|
||||||
|
.WhereIf(dataScope?.Count == 0, u => u.Value.DeviceRuntime.CreateUserId == UserManager.UserId).Select(a => a.Value);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static async Task<IEnumerable<VariableRuntime>> GetCurrentUserIdVariables()
|
public static async Task CheckByDeviceNames(IEnumerable<string> deviceNames)
|
||||||
{
|
{
|
||||||
var dataScope = await GlobalData.SysUserService.GetCurrentUserDataScopeAsync().ConfigureAwait(false);
|
List<long> orgids = new();
|
||||||
return IdVariables.Where(a => a.Value.IsInternalMemoryVariable == false).WhereIf(dataScope != null && dataScope?.Count > 0, u => dataScope.Contains(u.Value.CreateOrgId))//在指定机构列表查询
|
List<long> userIds = new();
|
||||||
.WhereIf(dataScope?.Count == 0, u => u.Value.CreateUserId == UserManager.UserId).Select(a => a.Value);
|
foreach (var deviceData in GlobalData.Devices.FilterByKeys(deviceNames))
|
||||||
|
{
|
||||||
|
orgids.Add(deviceData.Value.CreateOrgId);
|
||||||
|
userIds.Add(deviceData.Value.CreateUserId);
|
||||||
|
}
|
||||||
|
await GlobalData.SysUserService.CheckApiDataScopeAsync(orgids, userIds).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
public static async Task CheckByDeviceIds(IEnumerable<long> deviceIds)
|
||||||
|
{
|
||||||
|
List<long> orgids = new();
|
||||||
|
List<long> userIds = new();
|
||||||
|
foreach (var deviceData in GlobalData.IdDevices.FilterByKeys(deviceIds))
|
||||||
|
{
|
||||||
|
orgids.Add(deviceData.Value.CreateOrgId);
|
||||||
|
userIds.Add(deviceData.Value.CreateUserId);
|
||||||
|
}
|
||||||
|
await GlobalData.SysUserService.CheckApiDataScopeAsync(orgids, userIds).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
public static async Task CheckByVariableIds(IEnumerable<long> variableIds)
|
||||||
|
{
|
||||||
|
List<long> orgids = new();
|
||||||
|
List<long> userIds = new();
|
||||||
|
foreach (var deviceData in GlobalData.IdVariables.FilterByKeys(variableIds))
|
||||||
|
{
|
||||||
|
orgids.Add(deviceData.Value.DeviceRuntime.CreateOrgId);
|
||||||
|
userIds.Add(deviceData.Value.DeviceRuntime.CreateUserId);
|
||||||
|
}
|
||||||
|
await GlobalData.SysUserService.CheckApiDataScopeAsync(orgids, userIds).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
public static async Task CheckByVariableId(long variableId)
|
||||||
|
{
|
||||||
|
if (GlobalData.IdVariables.TryGetValue(variableId, out var variable))
|
||||||
|
{
|
||||||
|
await GlobalData.SysUserService.CheckApiDataScopeAsync(variable.DeviceRuntime.CreateOrgId, variable.DeviceRuntime.CreateUserId).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public static Task<IEnumerable<AlarmVariable>> GetCurrentUserRealAlarmVariablesAsync()
|
||||||
|
{
|
||||||
|
return GetCurrentUserRealAlarmVariablesAsync();
|
||||||
|
|
||||||
|
static async PooledTask<IEnumerable<AlarmVariable>> GetCurrentUserRealAlarmVariablesAsync()
|
||||||
|
{
|
||||||
|
var dataScope = await GlobalData.SysUserService.GetCurrentUserDataScopeAsync().ConfigureAwait(false);
|
||||||
|
return RealAlarmIdVariables
|
||||||
|
.WhereIf(dataScope != null && dataScope?.Count > 0, u => dataScope.Contains(u.Value.CreateOrgId))//在指定机构列表查询
|
||||||
|
.WhereIf(dataScope?.Count == 0, u => u.Value.CreateUserId == UserManager.UserId).Select(a => a.Value);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static async Task<IEnumerable<AlarmVariable>> GetCurrentUserRealAlarmVariablesAsync()
|
public static Task<IEnumerable<VariableRuntime>> GetCurrentUserAlarmEnableVariables()
|
||||||
{
|
{
|
||||||
var dataScope = await GlobalData.SysUserService.GetCurrentUserDataScopeAsync().ConfigureAwait(false);
|
return GetCurrentUserAlarmEnableVariables();
|
||||||
return RealAlarmIdVariables.WhereIf(dataScope != null && dataScope?.Count > 0, u => dataScope.Contains(u.Value.CreateOrgId))//在指定机构列表查询
|
|
||||||
.WhereIf(dataScope?.Count == 0, u => u.Value.CreateUserId == UserManager.UserId).Select(a => a.Value);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static async Task<IEnumerable<VariableRuntime>> GetCurrentUserAlarmEnableVariables()
|
static async PooledTask<IEnumerable<VariableRuntime>> GetCurrentUserAlarmEnableVariables()
|
||||||
{
|
{
|
||||||
var dataScope = await GlobalData.SysUserService.GetCurrentUserDataScopeAsync().ConfigureAwait(false);
|
var dataScope = await GlobalData.SysUserService.GetCurrentUserDataScopeAsync().ConfigureAwait(false);
|
||||||
return AlarmEnableIdVariables.Where(a => a.Value.IsInternalMemoryVariable == false).WhereIf(dataScope != null && dataScope?.Count > 0, u => dataScope.Contains(u.Value.CreateOrgId))//在指定机构列表查询
|
return AlarmEnableIdVariables.Where(a => a.Value.IsInternalMemoryVariable == false).WhereIf(dataScope != null && dataScope?.Count > 0, u => dataScope.Contains(u.Value.DeviceRuntime.CreateOrgId))//在指定机构列表查询
|
||||||
.WhereIf(dataScope?.Count == 0, u => u.Value.CreateUserId == UserManager.UserId).Select(a => a.Value);
|
.WhereIf(dataScope?.Count == 0, u => u.Value.DeviceRuntime.CreateUserId == UserManager.UserId).Select(a => a.Value);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static bool ContainsVariable(long businessDeviceId, VariableRuntime a)
|
public static bool ContainsVariable(long businessDeviceId, VariableRuntime a)
|
||||||
|
|||||||
@@ -26,6 +26,8 @@ public static partial class GatewayMapper
|
|||||||
[MapProperty($"{nameof(VariableRuntime.AlarmRuntimePropertys)}.{nameof(AlarmRuntimePropertys.EventTime)}", nameof(AlarmVariable.EventTime))]
|
[MapProperty($"{nameof(VariableRuntime.AlarmRuntimePropertys)}.{nameof(AlarmRuntimePropertys.EventTime)}", nameof(AlarmVariable.EventTime))]
|
||||||
[MapProperty($"{nameof(VariableRuntime.AlarmRuntimePropertys)}.{nameof(AlarmRuntimePropertys.AlarmType)}", nameof(AlarmVariable.AlarmType))]
|
[MapProperty($"{nameof(VariableRuntime.AlarmRuntimePropertys)}.{nameof(AlarmRuntimePropertys.AlarmType)}", nameof(AlarmVariable.AlarmType))]
|
||||||
[MapProperty($"{nameof(VariableRuntime.AlarmRuntimePropertys)}.{nameof(AlarmRuntimePropertys.EventType)}", nameof(AlarmVariable.EventType))]
|
[MapProperty($"{nameof(VariableRuntime.AlarmRuntimePropertys)}.{nameof(AlarmRuntimePropertys.EventType)}", nameof(AlarmVariable.EventType))]
|
||||||
|
[MapProperty($"{nameof(VariableRuntime.DeviceRuntime)}.{nameof(DeviceRuntime.CreateOrgId)}", nameof(AlarmVariable.CreateOrgId))]
|
||||||
|
[MapProperty($"{nameof(VariableRuntime.DeviceRuntime)}.{nameof(DeviceRuntime.CreateUserId)}", nameof(AlarmVariable.CreateUserId))]
|
||||||
public static partial AlarmVariable AdaptAlarmVariable(this VariableRuntime src);
|
public static partial AlarmVariable AdaptAlarmVariable(this VariableRuntime src);
|
||||||
|
|
||||||
public static partial VariableDataWithValue AdaptVariableDataWithValue(this VariableBasicData src);
|
public static partial VariableDataWithValue AdaptVariableDataWithValue(this VariableBasicData src);
|
||||||
|
|||||||
@@ -8,6 +8,8 @@
|
|||||||
// QQ群:605534569
|
// QQ群:605534569
|
||||||
//------------------------------------------------------------------------------
|
//------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
using PooledAwait;
|
||||||
|
|
||||||
using TouchSocket.Core;
|
using TouchSocket.Core;
|
||||||
|
|
||||||
namespace ThingsGateway.Gateway.Application;
|
namespace ThingsGateway.Gateway.Application;
|
||||||
@@ -51,42 +53,47 @@ public class VariableMethod
|
|||||||
/// <param name="value">以,逗号分割的参数</param>
|
/// <param name="value">以,逗号分割的参数</param>
|
||||||
/// <param name="cancellationToken">取消令箭</param>
|
/// <param name="cancellationToken">取消令箭</param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
public async ValueTask<IOperResult> InvokeMethodAsync(object driverBase, string? value = null, CancellationToken cancellationToken = default)
|
public ValueTask<IOperResult> InvokeMethodAsync(object driverBase, string? value = null, CancellationToken cancellationToken = default)
|
||||||
{
|
{
|
||||||
try
|
return InvokeMethodAsync(this, driverBase, value, cancellationToken);
|
||||||
|
|
||||||
|
static async PooledValueTask<IOperResult> InvokeMethodAsync(VariableMethod @this, object driverBase, string? value, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
object?[]? os = null;
|
try
|
||||||
if (value == null && OS == null)
|
|
||||||
{
|
{
|
||||||
//默认的参数
|
object?[]? os = null;
|
||||||
var addresss = Variable.RegisterAddress.SplitOS();
|
if (value == null && @this.OS == null)
|
||||||
//通过逗号分割,并且合并参数
|
{
|
||||||
var strs = addresss;
|
//默认的参数
|
||||||
|
var addresss = @this.Variable.RegisterAddress.SplitOS();
|
||||||
|
//通过逗号分割,并且合并参数
|
||||||
|
var strs = addresss;
|
||||||
|
|
||||||
OS = GetOS(strs, cancellationToken);
|
@this.OS = @this.GetOS(strs, cancellationToken);
|
||||||
os = OS;
|
os = @this.OS;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var addresss = @this.Variable.RegisterAddress.SplitOS();
|
||||||
|
var values = value.SplitOS();
|
||||||
|
//通过分号分割,并且合并参数
|
||||||
|
var strs = addresss.Concat(values).ToList();
|
||||||
|
os = @this.GetOS(strs, cancellationToken);
|
||||||
|
}
|
||||||
|
|
||||||
|
dynamic result;
|
||||||
|
|
||||||
|
result = await @this.MethodInfo.InvokeAsync(driverBase, os).ConfigureAwait(false);
|
||||||
|
if (@this.MethodInfo.HasReturn)
|
||||||
|
{
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
return OperResult.Success;
|
||||||
}
|
}
|
||||||
else
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
var addresss = Variable.RegisterAddress.SplitOS();
|
return new OperResult(ex);
|
||||||
var values = value.SplitOS();
|
|
||||||
//通过分号分割,并且合并参数
|
|
||||||
var strs = addresss.Concat(values).ToList();
|
|
||||||
os = GetOS(strs, cancellationToken);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
dynamic result;
|
|
||||||
|
|
||||||
result = await MethodInfo.InvokeAsync(driverBase, os).ConfigureAwait(false);
|
|
||||||
if (MethodInfo.HasReturn)
|
|
||||||
{
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
return OperResult.Success;
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
return new OperResult(ex);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -12,6 +12,8 @@ using BootstrapBlazor.Components;
|
|||||||
|
|
||||||
using Newtonsoft.Json.Linq;
|
using Newtonsoft.Json.Linq;
|
||||||
|
|
||||||
|
using PooledAwait;
|
||||||
|
|
||||||
using Riok.Mapperly.Abstractions;
|
using Riok.Mapperly.Abstractions;
|
||||||
|
|
||||||
|
|
||||||
@@ -64,11 +66,6 @@ public partial class VariableRuntime : Variable
|
|||||||
[AutoGenerateColumn(Visible = true, Filterable = true, Sortable = true, Order = 5)]
|
[AutoGenerateColumn(Visible = true, Filterable = true, Sortable = true, Order = 5)]
|
||||||
public DateTime CollectTime { get => collectTime; set => collectTime = value; }
|
public DateTime CollectTime { get => collectTime; set => collectTime = value; }
|
||||||
|
|
||||||
[SugarColumn(ColumnDescription = "排序码", IsNullable = true)]
|
|
||||||
[AutoGenerateColumn(Visible = false, DefaultSort = false, Sortable = true)]
|
|
||||||
[IgnoreExcel]
|
|
||||||
public override int SortCode { get => sortCode; set => sortCode = value; }
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 上次值
|
/// 上次值
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -243,7 +240,6 @@ public partial class VariableRuntime : Variable
|
|||||||
|
|
||||||
|
|
||||||
private int index;
|
private int index;
|
||||||
private int sortCode;
|
|
||||||
private DateTime changeTime = DateTime.UnixEpoch.ToLocalTime();
|
private DateTime changeTime = DateTime.UnixEpoch.ToLocalTime();
|
||||||
|
|
||||||
private DateTime collectTime = DateTime.UnixEpoch.ToLocalTime();
|
private DateTime collectTime = DateTime.UnixEpoch.ToLocalTime();
|
||||||
@@ -448,13 +444,18 @@ public partial class VariableRuntime : Variable
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public virtual async ValueTask<IOperResult> RpcAsync(string value, string executive = "brower", CancellationToken cancellationToken = default)
|
public virtual ValueTask<IOperResult> RpcAsync(string value, string executive = "brower", CancellationToken cancellationToken = default)
|
||||||
{
|
{
|
||||||
var data = await GlobalData.RpcService.InvokeDeviceMethodAsync(executive, new Dictionary<string, Dictionary<string, string>>()
|
return RpcAsync(DeviceName, Name, value, executive, cancellationToken);
|
||||||
|
|
||||||
|
static async PooledValueTask<IOperResult> RpcAsync(string deviceName, string name, string value, string executive, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
{ DeviceName, new Dictionary<string, string>() { { Name,value} } }
|
var data = await GlobalData.RpcService.InvokeDeviceMethodAsync(executive, new Dictionary<string, Dictionary<string, string>>()
|
||||||
|
{
|
||||||
|
{ deviceName, new Dictionary<string, string>() { { name,value} } }
|
||||||
}, cancellationToken).ConfigureAwait(false);
|
}, cancellationToken).ConfigureAwait(false);
|
||||||
return data.FirstOrDefault().Value.FirstOrDefault().Value;
|
return data.FirstOrDefault().Value.FirstOrDefault().Value;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void SetErrorMessage(string lastErrorMessage)
|
public void SetErrorMessage(string lastErrorMessage)
|
||||||
|
|||||||
@@ -13,6 +13,8 @@ using BootstrapBlazor.Components;
|
|||||||
using Microsoft.Extensions.Hosting;
|
using Microsoft.Extensions.Hosting;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
||||||
|
using PooledAwait;
|
||||||
|
|
||||||
using ThingsGateway.Blazor.Diagrams.Core;
|
using ThingsGateway.Blazor.Diagrams.Core;
|
||||||
using ThingsGateway.Blazor.Diagrams.Core.Models;
|
using ThingsGateway.Blazor.Diagrams.Core.Models;
|
||||||
using ThingsGateway.NewLife;
|
using ThingsGateway.NewLife;
|
||||||
@@ -139,68 +141,76 @@ internal sealed class RulesEngineHostedService : BackgroundService, IRulesEngine
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static async Task Analysis(NodeModel targetNode, NodeInput input, RulesLog rulesLog, CancellationToken cancellationToken)
|
private static Task Analysis(NodeModel targetNode, NodeInput input, RulesLog rulesLog, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
if (targetNode is INode node)
|
return Analysis(targetNode, input, rulesLog, cancellationToken);
|
||||||
{
|
|
||||||
node.Logger = rulesLog.Log;
|
|
||||||
node.RulesEngineName = rulesLog.Rules.Name;
|
|
||||||
}
|
|
||||||
|
|
||||||
try
|
|
||||||
|
static async PooledTask Analysis(NodeModel targetNode, NodeInput input, RulesLog rulesLog, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
if (targetNode == null)
|
if (targetNode is INode node)
|
||||||
return;
|
|
||||||
if (targetNode is IConditionNode conditionNode)
|
|
||||||
{
|
{
|
||||||
var next = await conditionNode.ExecuteAsync(input, cancellationToken).ConfigureAwait(false);
|
node.Logger = rulesLog.Log;
|
||||||
if (next)
|
node.RulesEngineName = rulesLog.Rules.Name;
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (targetNode == null)
|
||||||
|
return;
|
||||||
|
if (targetNode is IConditionNode conditionNode)
|
||||||
{
|
{
|
||||||
foreach (var link in targetNode.PortLinks.Where(a => ((a.Target.Model as PortModel)?.Parent) != targetNode))
|
var next = await conditionNode.ExecuteAsync(input, cancellationToken).ConfigureAwait(false);
|
||||||
|
if (next)
|
||||||
{
|
{
|
||||||
await Analysis((link.Target.Model as PortModel)?.Parent, input, rulesLog, cancellationToken).ConfigureAwait(false);
|
foreach (var link in targetNode.PortLinks.Where(a => ((a.Target.Model as PortModel)?.Parent) != targetNode))
|
||||||
|
{
|
||||||
|
await RulesEngineHostedService.Analysis((link.Target.Model as PortModel)?.Parent, input, rulesLog, cancellationToken).ConfigureAwait(false);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
else if (targetNode is IExpressionNode expressionNode)
|
||||||
else if (targetNode is IExpressionNode expressionNode)
|
|
||||||
{
|
|
||||||
var nodeOutput = await expressionNode.ExecuteAsync(input, cancellationToken).ConfigureAwait(false);
|
|
||||||
if (nodeOutput.IsSuccess)
|
|
||||||
{
|
{
|
||||||
foreach (var link in targetNode.PortLinks.Where(a => ((a.Target.Model as PortModel)?.Parent) != targetNode))
|
var nodeOutput = await expressionNode.ExecuteAsync(input, cancellationToken).ConfigureAwait(false);
|
||||||
|
if (nodeOutput.IsSuccess)
|
||||||
{
|
{
|
||||||
await Analysis((link.Target.Model as PortModel)?.Parent, new NodeInput() { Value = nodeOutput.Content.Value, }, rulesLog, cancellationToken).ConfigureAwait(false);
|
foreach (var link in targetNode.PortLinks.Where(a => ((a.Target.Model as PortModel)?.Parent) != targetNode))
|
||||||
|
{
|
||||||
|
await RulesEngineHostedService.Analysis((link.Target.Model as PortModel)?.Parent, new NodeInput() { Value = nodeOutput.Content.Value, }, rulesLog, cancellationToken).ConfigureAwait(false);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
else if (targetNode is IActuatorNode actuatorNode)
|
||||||
else if (targetNode is IActuatorNode actuatorNode)
|
|
||||||
{
|
|
||||||
var nodeOutput = await actuatorNode.ExecuteAsync(input, cancellationToken).ConfigureAwait(false);
|
|
||||||
if (nodeOutput.IsSuccess)
|
|
||||||
{
|
{
|
||||||
foreach (var link in targetNode.PortLinks.Where(a => ((a.Target.Model as PortModel)?.Parent) != targetNode))
|
var nodeOutput = await actuatorNode.ExecuteAsync(input, cancellationToken).ConfigureAwait(false);
|
||||||
|
if (nodeOutput.IsSuccess)
|
||||||
{
|
{
|
||||||
await Analysis((link.Target.Model as PortModel)?.Parent, new NodeInput() { Value = nodeOutput.Content.Value }, rulesLog, cancellationToken).ConfigureAwait(false);
|
foreach (var link in targetNode.PortLinks.Where(a => ((a.Target.Model as PortModel)?.Parent) != targetNode))
|
||||||
|
{
|
||||||
|
await RulesEngineHostedService.Analysis((link.Target.Model as PortModel)?.Parent, new NodeInput() { Value = nodeOutput.Content.Value }, rulesLog, cancellationToken).ConfigureAwait(false);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
else if (targetNode is ITriggerNode triggerNode)
|
||||||
else if (targetNode is ITriggerNode triggerNode)
|
|
||||||
{
|
|
||||||
Func<NodeOutput, CancellationToken, Task> func = (async (a, token) =>
|
|
||||||
{
|
{
|
||||||
foreach (var link in targetNode.PortLinks.Where(a => ((a.Target.Model as PortModel)?.Parent) != targetNode))
|
Func<NodeOutput, CancellationToken, Task> func = (async (a, token) =>
|
||||||
{
|
{
|
||||||
await Analysis((link.Target.Model as PortModel)?.Parent, new NodeInput() { Value = a.Value }, rulesLog, token).ConfigureAwait(false);
|
foreach (var link in targetNode.PortLinks.Where(a => ((a.Target.Model as PortModel)?.Parent) != targetNode))
|
||||||
}
|
{
|
||||||
});
|
await RulesEngineHostedService.Analysis((link.Target.Model as PortModel)?.Parent, new NodeInput() { Value = a.Value }, rulesLog, token).ConfigureAwait(false);
|
||||||
await triggerNode.StartAsync(func, cancellationToken).ConfigureAwait(false);
|
}
|
||||||
|
});
|
||||||
|
await triggerNode.StartAsync(func, cancellationToken).ConfigureAwait(false);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
catch (TaskCanceledException) { }
|
||||||
catch (TaskCanceledException) { }
|
catch (OperationCanceledException) { }
|
||||||
catch (OperationCanceledException) { }
|
catch (Exception ex)
|
||||||
catch (Exception ex)
|
{
|
||||||
{
|
rulesLog.Log?.LogWarning(ex);
|
||||||
rulesLog.Log?.LogWarning(ex);
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -22,7 +22,6 @@ using System.Text;
|
|||||||
|
|
||||||
using ThingsGateway.Common.Extension;
|
using ThingsGateway.Common.Extension;
|
||||||
using ThingsGateway.Common.Extension.Generic;
|
using ThingsGateway.Common.Extension.Generic;
|
||||||
using ThingsGateway.Extension.Generic;
|
|
||||||
using ThingsGateway.Foundation.Extension.Dynamic;
|
using ThingsGateway.Foundation.Extension.Dynamic;
|
||||||
|
|
||||||
using TouchSocket.Core;
|
using TouchSocket.Core;
|
||||||
@@ -107,8 +106,6 @@ internal sealed class VariableService : BaseService<Variable>, IVariableService
|
|||||||
variable.DataType = DataTypeEnum.Int16;
|
variable.DataType = DataTypeEnum.Int16;
|
||||||
variable.Name = name;
|
variable.Name = name;
|
||||||
variable.Id = id;
|
variable.Id = id;
|
||||||
variable.CreateOrgId = UserManager.OrgId;
|
|
||||||
variable.CreateUserId = UserManager.UserId;
|
|
||||||
variable.DeviceId = device.Id;
|
variable.DeviceId = device.Id;
|
||||||
variable.RegisterAddress = address;
|
variable.RegisterAddress = address;
|
||||||
newVariables.Add(variable);
|
newVariables.Add(variable);
|
||||||
@@ -334,8 +331,6 @@ internal sealed class VariableService : BaseService<Variable>, IVariableService
|
|||||||
variable.DataType = DataTypeEnum.Int16;
|
variable.DataType = DataTypeEnum.Int16;
|
||||||
variable.Name = name;
|
variable.Name = name;
|
||||||
variable.Id = id;
|
variable.Id = id;
|
||||||
variable.CreateOrgId = UserManager.OrgId;
|
|
||||||
variable.CreateUserId = UserManager.UserId;
|
|
||||||
variable.DeviceId = device.Id;
|
variable.DeviceId = device.Id;
|
||||||
variable.RegisterAddress = address;
|
variable.RegisterAddress = address;
|
||||||
newVariables.Add(variable);
|
newVariables.Add(variable);
|
||||||
@@ -428,12 +423,9 @@ internal sealed class VariableService : BaseService<Variable>, IVariableService
|
|||||||
differences.Remove(nameof(Variable.VariablePropertys));
|
differences.Remove(nameof(Variable.VariablePropertys));
|
||||||
if (differences?.Count > 0)
|
if (differences?.Count > 0)
|
||||||
{
|
{
|
||||||
|
var data = models.ToList();
|
||||||
|
await GlobalData.CheckByDeviceIds(data.Select(a => a.DeviceId)).ConfigureAwait(false);
|
||||||
using var db = GetDB();
|
using var db = GetDB();
|
||||||
var dataScope = await GlobalData.SysUserService.GetCurrentUserDataScopeAsync().ConfigureAwait(false);
|
|
||||||
var data = models
|
|
||||||
.WhereIf(dataScope != null && dataScope?.Count > 0, u => dataScope.Contains(u.CreateOrgId))//在指定机构列表查询
|
|
||||||
.WhereIf(dataScope?.Count == 0, u => u.CreateUserId == UserManager.UserId)
|
|
||||||
.ToList();
|
|
||||||
|
|
||||||
var result = (await db.Updateable(data).UpdateColumns(differences.Select(a => a.Key).ToArray()).ExecuteCommandAsync().ConfigureAwait(false)) > 0;
|
var result = (await db.Updateable(data).UpdateColumns(differences.Select(a => a.Key).ToArray()).ExecuteCommandAsync().ConfigureAwait(false)) > 0;
|
||||||
|
|
||||||
@@ -448,24 +440,20 @@ internal sealed class VariableService : BaseService<Variable>, IVariableService
|
|||||||
[OperDesc("DeleteVariable", isRecordPar: false, localizerType: typeof(Variable))]
|
[OperDesc("DeleteVariable", isRecordPar: false, localizerType: typeof(Variable))]
|
||||||
public async Task DeleteByDeviceIdAsync(IEnumerable<long> input, SqlSugarClient db)
|
public async Task DeleteByDeviceIdAsync(IEnumerable<long> input, SqlSugarClient db)
|
||||||
{
|
{
|
||||||
var dataScope = await GlobalData.SysUserService.GetCurrentUserDataScopeAsync().ConfigureAwait(false);
|
|
||||||
var ids = input.ToList();
|
var ids = input.ToList();
|
||||||
var result = await db.Deleteable<Variable>().Where(a => ids.Contains(a.DeviceId))
|
await GlobalData.CheckByDeviceIds(ids).ConfigureAwait(false);
|
||||||
.WhereIF(dataScope != null && dataScope?.Count > 0, u => dataScope.Contains(u.CreateOrgId))//在指定机构列表查询
|
|
||||||
.WhereIF(dataScope?.Count == 0, u => u.CreateUserId == UserManager.UserId)
|
|
||||||
.ExecuteCommandAsync().ConfigureAwait(false);
|
|
||||||
|
|
||||||
|
var result = await db.Deleteable<Variable>().Where(a => ids.Contains(a.DeviceId))
|
||||||
|
.ExecuteCommandAsync().ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
[OperDesc("DeleteVariable", isRecordPar: false, localizerType: typeof(Variable))]
|
[OperDesc("DeleteVariable", isRecordPar: false, localizerType: typeof(Variable))]
|
||||||
public async Task<bool> DeleteVariableAsync(IEnumerable<long> input)
|
public async Task<bool> DeleteVariableAsync(IEnumerable<long> input)
|
||||||
{
|
{
|
||||||
using var db = GetDB();
|
using var db = GetDB();
|
||||||
var dataScope = await GlobalData.SysUserService.GetCurrentUserDataScopeAsync().ConfigureAwait(false);
|
|
||||||
var ids = input?.ToList();
|
var ids = input?.ToList();
|
||||||
|
await GlobalData.CheckByVariableIds(ids).ConfigureAwait(false);
|
||||||
var result = (await db.Deleteable<Variable>().WhereIF(input != null, a => ids.Contains(a.Id))
|
var result = (await db.Deleteable<Variable>().WhereIF(input != null, a => ids.Contains(a.Id))
|
||||||
.WhereIF(dataScope != null && dataScope?.Count > 0, u => dataScope.Contains(u.CreateOrgId))//在指定机构列表查询
|
|
||||||
.WhereIF(dataScope?.Count == 0, u => u.CreateUserId == UserManager.UserId)
|
|
||||||
.ExecuteCommandAsync().ConfigureAwait(false)) > 0;
|
.ExecuteCommandAsync().ConfigureAwait(false)) > 0;
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
@@ -505,6 +493,11 @@ internal sealed class VariableService : BaseService<Variable>, IVariableService
|
|||||||
private async Task<Func<ISugarQueryable<Variable>, ISugarQueryable<Variable>>> GetWhereQueryFunc(GatewayExportFilter exportFilter)
|
private async Task<Func<ISugarQueryable<Variable>, ISugarQueryable<Variable>>> GetWhereQueryFunc(GatewayExportFilter exportFilter)
|
||||||
{
|
{
|
||||||
var dataScope = await GlobalData.SysUserService.GetCurrentUserDataScopeAsync().ConfigureAwait(false);
|
var dataScope = await GlobalData.SysUserService.GetCurrentUserDataScopeAsync().ConfigureAwait(false);
|
||||||
|
List<long>? filterDeviceIds= null;
|
||||||
|
if(dataScope!=null)
|
||||||
|
{
|
||||||
|
filterDeviceIds= GlobalData.GetCurrentUserDeviceIds(dataScope).ToList();
|
||||||
|
}
|
||||||
HashSet<long>? deviceId = null;
|
HashSet<long>? deviceId = null;
|
||||||
if (!exportFilter.PluginName.IsNullOrWhiteSpace())
|
if (!exportFilter.PluginName.IsNullOrWhiteSpace())
|
||||||
{
|
{
|
||||||
@@ -520,8 +513,7 @@ internal sealed class VariableService : BaseService<Variable>, IVariableService
|
|||||||
.WhereIF(exportFilter.PluginType == PluginTypeEnum.Collect, a => a.DeviceId == exportFilter.DeviceId)
|
.WhereIF(exportFilter.PluginType == PluginTypeEnum.Collect, a => a.DeviceId == exportFilter.DeviceId)
|
||||||
.WhereIF(deviceId != null, a => deviceId.Contains(a.DeviceId))
|
.WhereIF(deviceId != null, a => deviceId.Contains(a.DeviceId))
|
||||||
|
|
||||||
.WhereIF(dataScope != null && dataScope?.Count > 0, u => dataScope.Contains(u.CreateOrgId))//在指定机构列表查询
|
.WhereIF(filterDeviceIds != null , u => filterDeviceIds.Contains(u.DeviceId))//在指定机构列表查询
|
||||||
.WhereIF(dataScope?.Count == 0, u => u.CreateUserId == UserManager.UserId)
|
|
||||||
|
|
||||||
.WhereIF(exportFilter.PluginType == PluginTypeEnum.Business, u => SqlFunc.JsonLike(u.VariablePropertys, exportFilter.DeviceId.ToString()));
|
.WhereIF(exportFilter.PluginType == PluginTypeEnum.Business, u => SqlFunc.JsonLike(u.VariablePropertys, exportFilter.DeviceId.ToString()));
|
||||||
return whereQuery;
|
return whereQuery;
|
||||||
@@ -530,6 +522,13 @@ internal sealed class VariableService : BaseService<Variable>, IVariableService
|
|||||||
private async Task<Func<IEnumerable<Variable>, IEnumerable<Variable>>> GetWhereEnumerableFunc(GatewayExportFilter exportFilter, bool sql = false)
|
private async Task<Func<IEnumerable<Variable>, IEnumerable<Variable>>> GetWhereEnumerableFunc(GatewayExportFilter exportFilter, bool sql = false)
|
||||||
{
|
{
|
||||||
var dataScope = await GlobalData.SysUserService.GetCurrentUserDataScopeAsync().ConfigureAwait(false);
|
var dataScope = await GlobalData.SysUserService.GetCurrentUserDataScopeAsync().ConfigureAwait(false);
|
||||||
|
List<long>? filterDeviceIds = null;
|
||||||
|
if (dataScope != null)
|
||||||
|
{
|
||||||
|
filterDeviceIds = GlobalData.GetCurrentUserDeviceIds(dataScope).ToList();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
HashSet<long>? deviceId = null;
|
HashSet<long>? deviceId = null;
|
||||||
if (!exportFilter.PluginName.IsNullOrWhiteSpace())
|
if (!exportFilter.PluginName.IsNullOrWhiteSpace())
|
||||||
{
|
{
|
||||||
@@ -545,8 +544,7 @@ internal sealed class VariableService : BaseService<Variable>, IVariableService
|
|||||||
.WhereIF(exportFilter.PluginType == PluginTypeEnum.Collect, a => a.DeviceId == exportFilter.DeviceId)
|
.WhereIF(exportFilter.PluginType == PluginTypeEnum.Collect, a => a.DeviceId == exportFilter.DeviceId)
|
||||||
.WhereIF(deviceId != null, a => deviceId.Contains(a.DeviceId))
|
.WhereIF(deviceId != null, a => deviceId.Contains(a.DeviceId))
|
||||||
|
|
||||||
.WhereIF(dataScope != null && dataScope?.Count > 0, u => dataScope.Contains(u.CreateOrgId))//在指定机构列表查询
|
.WhereIF(filterDeviceIds != null, u => filterDeviceIds.Contains(u.DeviceId))//在指定机构列表查询
|
||||||
.WhereIF(dataScope?.Count == 0, u => u.CreateUserId == UserManager.UserId)
|
|
||||||
|
|
||||||
.WhereIF(sql && exportFilter.PluginType == PluginTypeEnum.Business, u => SqlFunc.JsonLike(u.VariablePropertys, exportFilter.DeviceId.ToString()))
|
.WhereIF(sql && exportFilter.PluginType == PluginTypeEnum.Business, u => SqlFunc.JsonLike(u.VariablePropertys, exportFilter.DeviceId.ToString()))
|
||||||
.WhereIF(!sql && exportFilter.PluginType == PluginTypeEnum.Business && exportFilter.DeviceId > 0, u =>
|
.WhereIF(!sql && exportFilter.PluginType == PluginTypeEnum.Business && exportFilter.DeviceId > 0, u =>
|
||||||
@@ -566,7 +564,7 @@ internal sealed class VariableService : BaseService<Variable>, IVariableService
|
|||||||
public async Task<bool> SaveVariableAsync(Variable input, ItemChangedType type)
|
public async Task<bool> SaveVariableAsync(Variable input, ItemChangedType type)
|
||||||
{
|
{
|
||||||
if (type == ItemChangedType.Update)
|
if (type == ItemChangedType.Update)
|
||||||
await GlobalData.SysUserService.CheckApiDataScopeAsync(input.CreateOrgId, input.CreateUserId).ConfigureAwait(false);
|
await GlobalData.CheckByVariableId(input.Id).ConfigureAwait(false);
|
||||||
else
|
else
|
||||||
ManageHelper.CheckVariableCount(1);
|
ManageHelper.CheckVariableCount(1);
|
||||||
|
|
||||||
@@ -767,6 +765,13 @@ internal sealed class VariableService : BaseService<Variable>, IVariableService
|
|||||||
|
|
||||||
public ImportPreviewOutput<Dictionary<string, Variable>> SetVariableData(HashSet<long>? dataScope, IReadOnlyDictionary<string, DeviceRuntime> deviceDicts, Dictionary<string, ImportPreviewOutputBase> ImportPreviews, ImportPreviewOutput<Dictionary<string, Variable>> deviceImportPreview, Dictionary<string, PluginInfo> driverPluginNameDict, NonBlockingDictionary<string, (Type, Dictionary<string, PropertyInfo>, Dictionary<string, PropertyInfo>)> propertysDict, string sheetName, IEnumerable<IDictionary<string, object>> rows)
|
public ImportPreviewOutput<Dictionary<string, Variable>> SetVariableData(HashSet<long>? dataScope, IReadOnlyDictionary<string, DeviceRuntime> deviceDicts, Dictionary<string, ImportPreviewOutputBase> ImportPreviews, ImportPreviewOutput<Dictionary<string, Variable>> deviceImportPreview, Dictionary<string, PluginInfo> driverPluginNameDict, NonBlockingDictionary<string, (Type, Dictionary<string, PropertyInfo>, Dictionary<string, PropertyInfo>)> propertysDict, string sheetName, IEnumerable<IDictionary<string, object>> rows)
|
||||||
{
|
{
|
||||||
|
|
||||||
|
List<long>? filterDeviceIds = null;
|
||||||
|
if (dataScope != null)
|
||||||
|
{
|
||||||
|
filterDeviceIds = GlobalData.GetCurrentUserDeviceIds(dataScope).ToList();
|
||||||
|
}
|
||||||
|
|
||||||
string ImportNullError = Localizer["ImportNullError"];
|
string ImportNullError = Localizer["ImportNullError"];
|
||||||
string RedundantDeviceError = Localizer["RedundantDeviceError"];
|
string RedundantDeviceError = Localizer["RedundantDeviceError"];
|
||||||
|
|
||||||
@@ -839,17 +844,14 @@ internal sealed class VariableService : BaseService<Variable>, IVariableService
|
|||||||
if (GlobalData.IdDevices.TryGetValue(variable.DeviceId, out var dbvar1s) && dbvar1s.VariableRuntimes.TryGetValue(variable.Name, out var dbvar1))
|
if (GlobalData.IdDevices.TryGetValue(variable.DeviceId, out var dbvar1s) && dbvar1s.VariableRuntimes.TryGetValue(variable.Name, out var dbvar1))
|
||||||
{
|
{
|
||||||
variable.Id = dbvar1.Id;
|
variable.Id = dbvar1.Id;
|
||||||
variable.CreateOrgId = dbvar1.CreateOrgId;
|
|
||||||
variable.CreateUserId = dbvar1.CreateUserId;
|
|
||||||
variable.IsUp = true;
|
variable.IsUp = true;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
variable.IsUp = false;
|
variable.IsUp = false;
|
||||||
variable.CreateOrgId = UserManager.OrgId;
|
|
||||||
variable.CreateUserId = UserManager.UserId;
|
|
||||||
}
|
}
|
||||||
if (device.IsUp && ((dataScope != null && dataScope?.Count > 0 && !dataScope.Contains(variable.CreateOrgId)) || dataScope?.Count == 0 && variable.CreateUserId != UserManager.UserId))
|
|
||||||
|
if (device.IsUp && (filterDeviceIds?.Contains(variable.DeviceId) != false))
|
||||||
{
|
{
|
||||||
importPreviewOutput.Results.Add(new(Interlocked.Increment(ref row), false, "Operation not permitted"));
|
importPreviewOutput.Results.Add(new(Interlocked.Increment(ref row), false, "Operation not permitted"));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,8 @@
|
|||||||
@using ThingsGateway.Admin.Application
|
@using ThingsGateway.Admin.Application
|
||||||
@using ThingsGateway.Admin.Razor
|
@using ThingsGateway.Admin.Razor
|
||||||
@using ThingsGateway.Gateway.Application
|
@using ThingsGateway.Gateway.Application
|
||||||
|
@attribute [JSModuleAutoLoader("Pages/GatewayMonitorPage/ChannelDeviceTree.razor.js", AutoInvokeInit = true, AutoInvokeDispose = false, JSObjectReference = true)]
|
||||||
|
@inherits ThingsGatewayModuleComponentBase
|
||||||
<div class="listtree-view">
|
<div class="listtree-view">
|
||||||
|
|
||||||
<div class="d-flex align-items-center">
|
<div class="d-flex align-items-center">
|
||||||
@@ -13,7 +14,7 @@
|
|||||||
|
|
||||||
|
|
||||||
<ContextMenuZone>
|
<ContextMenuZone>
|
||||||
<TreeView TItem="ChannelDeviceTreeItem" Items="Items" ShowIcon="false" ShowSearch IsAccordion=false IsVirtualize="true" OnTreeItemClick="OnTreeItemClick" OnSearchAsync="OnClickSearch" ModelEqualityComparer=ModelEqualityComparer ShowToolbar="true" >
|
<TreeView Id=@Id TItem="ChannelDeviceTreeItem" Items="Items" ShowIcon="false" ShowSearch IsAccordion=false IsVirtualize="true" OnTreeItemClick="OnTreeItemClick" OnSearchAsync="OnClickSearch" ModelEqualityComparer=ModelEqualityComparer ShowToolbar="true" >
|
||||||
<ToolbarTemplate>
|
<ToolbarTemplate>
|
||||||
|
|
||||||
<div class="tree-node-toolbar-edit" @onclick:preventDefault @onclick:stopPropagation>
|
<div class="tree-node-toolbar-edit" @onclick:preventDefault @onclick:stopPropagation>
|
||||||
@@ -137,6 +138,6 @@
|
|||||||
@code {
|
@code {
|
||||||
RenderFragment<ChannelDeviceTreeItem> RenderTreeItem = (item) =>
|
RenderFragment<ChannelDeviceTreeItem> RenderTreeItem = (item) =>
|
||||||
|
|
||||||
@<span class=@(GetClass(item))>@item.ToString()</span> ;
|
@<span class=@(GetClass(item)) id=@(GetId(item))>@item.ToString()</span> ;
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -10,6 +10,7 @@
|
|||||||
|
|
||||||
using Microsoft.AspNetCore.Components.Forms;
|
using Microsoft.AspNetCore.Components.Forms;
|
||||||
using Microsoft.AspNetCore.Components.Web;
|
using Microsoft.AspNetCore.Components.Web;
|
||||||
|
using Microsoft.JSInterop;
|
||||||
|
|
||||||
using ThingsGateway.Admin.Application;
|
using ThingsGateway.Admin.Application;
|
||||||
using ThingsGateway.Admin.Razor;
|
using ThingsGateway.Admin.Razor;
|
||||||
@@ -19,7 +20,7 @@ using ThingsGateway.SqlSugar;
|
|||||||
|
|
||||||
namespace ThingsGateway.Gateway.Razor;
|
namespace ThingsGateway.Gateway.Razor;
|
||||||
|
|
||||||
public partial class ChannelDeviceTree : IDisposable
|
public partial class ChannelDeviceTree
|
||||||
{
|
{
|
||||||
SpinnerComponent Spinner;
|
SpinnerComponent Spinner;
|
||||||
[Inject]
|
[Inject]
|
||||||
@@ -297,7 +298,34 @@ public partial class ChannelDeviceTree : IDisposable
|
|||||||
{
|
{
|
||||||
if (item.TryGetChannelRuntime(out var channelRuntime))
|
if (item.TryGetChannelRuntime(out var channelRuntime))
|
||||||
{
|
{
|
||||||
return channelRuntime.DeviceThreadManage != null ? "enable--text" : "disabled--text";
|
return channelRuntime.DeviceThreadManage != null ? " enable--text" : " disabled--text ";
|
||||||
|
}
|
||||||
|
else if (item.TryGetDeviceRuntime(out var deviceRuntime))
|
||||||
|
{
|
||||||
|
if (deviceRuntime.Driver?.DeviceThreadManage != null)
|
||||||
|
{
|
||||||
|
if (deviceRuntime.DeviceStatus == DeviceStatusEnum.OnLine)
|
||||||
|
{
|
||||||
|
return "green--text";
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return "red--text";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return "disabled--text";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return "enable--text";
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string GetClass(ChannelDeviceTreeItemStruct item)
|
||||||
|
{
|
||||||
|
if (item.TryGetChannelRuntime(out var channelRuntime))
|
||||||
|
{
|
||||||
|
return channelRuntime.DeviceThreadManage != null ? " enable--text" : " disabled--text ";
|
||||||
}
|
}
|
||||||
else if (item.TryGetDeviceRuntime(out var deviceRuntime))
|
else if (item.TryGetDeviceRuntime(out var deviceRuntime))
|
||||||
{
|
{
|
||||||
@@ -1391,24 +1419,6 @@ EventCallback.Factory.Create<MouseEventArgs>(this, async e =>
|
|||||||
|
|
||||||
scheduler = new SmartTriggerScheduler(Notify, TimeSpan.FromMilliseconds(3000));
|
scheduler = new SmartTriggerScheduler(Notify, TimeSpan.FromMilliseconds(3000));
|
||||||
|
|
||||||
_ = Task.Run(async () =>
|
|
||||||
{
|
|
||||||
while (!Disposed)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
await InvokeAsync(StateHasChanged);
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
await Task.Delay(5000);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
await base.OnInitializedAsync();
|
await base.OnInitializedAsync();
|
||||||
}
|
}
|
||||||
private async Task Notify(CancellationToken cancellationToken)
|
private async Task Notify(CancellationToken cancellationToken)
|
||||||
@@ -1423,7 +1433,7 @@ EventCallback.Factory.Create<MouseEventArgs>(this, async e =>
|
|||||||
{
|
{
|
||||||
await ChannelDeviceChanged.Invoke(Value);
|
await ChannelDeviceChanged.Invoke(Value);
|
||||||
}
|
}
|
||||||
|
await InvokeAsync(StateHasChanged);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static ChannelDeviceTreeItem GetValue(ChannelDeviceTreeItem channelDeviceTreeItem)
|
private static ChannelDeviceTreeItem GetValue(ChannelDeviceTreeItem channelDeviceTreeItem)
|
||||||
@@ -1595,10 +1605,17 @@ EventCallback.Factory.Create<MouseEventArgs>(this, async e =>
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
private bool Disposed;
|
private bool Disposed;
|
||||||
public void Dispose()
|
|
||||||
|
protected override async ValueTask DisposeAsync(bool disposing)
|
||||||
{
|
{
|
||||||
|
|
||||||
Disposed = true;
|
Disposed = true;
|
||||||
ChannelRuntimeDispatchService.UnSubscribe(Refresh);
|
ChannelRuntimeDispatchService?.UnSubscribe(Refresh);
|
||||||
|
|
||||||
|
if (Module != null)
|
||||||
|
await Module.InvokeVoidAsync("dispose", Id);
|
||||||
|
|
||||||
|
await base.DisposeAsync(disposing);
|
||||||
}
|
}
|
||||||
|
|
||||||
ChannelDeviceTreeItem? SelectModel = default;
|
ChannelDeviceTreeItem? SelectModel = default;
|
||||||
@@ -1616,5 +1633,28 @@ EventCallback.Factory.Create<MouseEventArgs>(this, async e =>
|
|||||||
return Task.CompletedTask;
|
return Task.CompletedTask;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#region js
|
||||||
|
|
||||||
|
protected override Task InvokeInitAsync() => InvokeVoidAsync("init", Id, Interop, new { Method = nameof(TriggerStateChanged) });
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
[JSInvokable]
|
||||||
|
public List<string> TriggerStateChanged(List<string> jsstring)
|
||||||
|
{
|
||||||
|
List<string> ret = new(jsstring.Count);
|
||||||
|
foreach (var str in jsstring)
|
||||||
|
{
|
||||||
|
var item = ChannelDeviceTreeItemStruct.FromJSString(str);
|
||||||
|
ret.Add(GetClass(item));
|
||||||
|
}
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string GetId(ChannelDeviceTreeItem channelDeviceTreeItem)
|
||||||
|
{
|
||||||
|
return ChannelDeviceTreeItem.ToJSString(channelDeviceTreeItem);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,57 @@
|
|||||||
|
let handlers = {};
|
||||||
|
|
||||||
|
export function init(id, invoke, options) {
|
||||||
|
//function getCellByClass(row, className) {
|
||||||
|
// // 直接用 querySelector 精确查找
|
||||||
|
// return row.querySelector(`td.${className}`);
|
||||||
|
//}
|
||||||
|
var variableHandler = setInterval(async () => {
|
||||||
|
var treeview = document.getElementById(id);
|
||||||
|
if (!treeview) {
|
||||||
|
clearInterval(variableHandler);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const spans = treeview.querySelectorAll(
|
||||||
|
'.tree-content[style*="--bb-tree-view-level: 2"] .tree-node > span, ' +
|
||||||
|
'.tree-content[style*="--bb-tree-view-level: 3"] .tree-node > span'
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!spans) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const ids = Array.from(spans).map(span => span.id);
|
||||||
|
|
||||||
|
var { method } = options;
|
||||||
|
|
||||||
|
if (!invoke) return;
|
||||||
|
var valss = await invoke.invokeMethodAsync(method, ids);
|
||||||
|
if (!valss || valss.length === 0) return;
|
||||||
|
// 遍历 valss,下标 i 对应 span[i]
|
||||||
|
for (let i = 0; i < valss.length && i < spans.length; i++) {
|
||||||
|
const val = valss[i];
|
||||||
|
const span = spans[i];
|
||||||
|
|
||||||
|
if (!span) continue;
|
||||||
|
|
||||||
|
if (span.className !== val) {
|
||||||
|
span.className = val;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
, 1000) //1000ms刷新一次
|
||||||
|
|
||||||
|
handlers[id] = { variableHandler, invoke };
|
||||||
|
|
||||||
|
}
|
||||||
|
export function dispose(id) {
|
||||||
|
const handler = handlers[id];
|
||||||
|
if (handler) {
|
||||||
|
clearInterval(handler.timer);
|
||||||
|
handler.invoke = null;
|
||||||
|
delete handlers[id];
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -141,4 +141,146 @@ public class ChannelDeviceTreeItem : IEqualityComparer<ChannelDeviceTreeItem>
|
|||||||
{
|
{
|
||||||
return HashCode.Combine(obj.ChannelDevicePluginType, obj.DeviceRuntimeId, obj.ChannelRuntimeId, obj.PluginName, obj.PluginType);
|
return HashCode.Combine(obj.ChannelDevicePluginType, obj.DeviceRuntimeId, obj.ChannelRuntimeId, obj.PluginName, obj.PluginType);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
public static string ToJSString(ChannelDeviceTreeItem channelDeviceTreeItem)
|
||||||
|
{
|
||||||
|
return $"{channelDeviceTreeItem.ChannelDevicePluginType}.{channelDeviceTreeItem.DeviceRuntimeId}.{channelDeviceTreeItem.ChannelRuntimeId}.{channelDeviceTreeItem.PluginName}.{channelDeviceTreeItem.PluginType}";
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public struct ChannelDeviceTreeItemStruct
|
||||||
|
{
|
||||||
|
public long Id { get; set; }
|
||||||
|
public ChannelDevicePluginTypeEnum ChannelDevicePluginType { get; set; }
|
||||||
|
|
||||||
|
public long DeviceRuntimeId { get; set; }
|
||||||
|
|
||||||
|
public long ChannelRuntimeId { get; set; }
|
||||||
|
public string PluginName { get; set; }
|
||||||
|
public PluginTypeEnum? PluginType { get; set; }
|
||||||
|
|
||||||
|
|
||||||
|
public bool TryGetDeviceRuntime(out DeviceRuntime deviceRuntime)
|
||||||
|
{
|
||||||
|
if (ChannelDevicePluginType == ChannelDevicePluginTypeEnum.Device && DeviceRuntimeId > 0)
|
||||||
|
{
|
||||||
|
if (GlobalData.ReadOnlyIdDevices.TryGetValue(DeviceRuntimeId, out deviceRuntime))
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
deviceRuntime = null;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool TryGetPluginName(out string pluginName)
|
||||||
|
{
|
||||||
|
if (ChannelDevicePluginType == ChannelDevicePluginTypeEnum.PluginName)
|
||||||
|
{
|
||||||
|
pluginName = PluginName;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
pluginName = default;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool TryGetPluginType(out PluginTypeEnum? pluginType)
|
||||||
|
{
|
||||||
|
if (ChannelDevicePluginType == ChannelDevicePluginTypeEnum.PluginType)
|
||||||
|
{
|
||||||
|
pluginType = PluginType;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
pluginType = default;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public bool TryGetChannelRuntime(out ChannelRuntime channelRuntime)
|
||||||
|
{
|
||||||
|
if (ChannelDevicePluginType == ChannelDevicePluginTypeEnum.Channel && ChannelRuntimeId > 0)
|
||||||
|
{
|
||||||
|
if (GlobalData.ReadOnlyIdChannels.TryGetValue(ChannelRuntimeId, out channelRuntime))
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
channelRuntime = null;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ChannelDeviceTreeItemStruct FromJSString(string jsString)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(jsString))
|
||||||
|
throw new ArgumentNullException(nameof(jsString));
|
||||||
|
|
||||||
|
ReadOnlySpan<char> span = jsString.AsSpan();
|
||||||
|
Span<Range> ranges = stackalloc Range[5];
|
||||||
|
|
||||||
|
// 手动分割
|
||||||
|
int partIndex = 0;
|
||||||
|
int start = 0;
|
||||||
|
while (partIndex < 4) // 只找前4个分隔符
|
||||||
|
{
|
||||||
|
int idx = span[start..].IndexOf('.');
|
||||||
|
if (idx == -1)
|
||||||
|
throw new FormatException($"Invalid format: expected 5 parts, got {partIndex + 1}");
|
||||||
|
|
||||||
|
ranges[partIndex] = new Range(start, start + idx);
|
||||||
|
start += idx + 1;
|
||||||
|
partIndex++;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 最后一段
|
||||||
|
ranges[partIndex] = new Range(start, span.Length);
|
||||||
|
|
||||||
|
// 校验段数
|
||||||
|
if (partIndex != 4)
|
||||||
|
throw new FormatException($"Invalid format: expected 5 parts, got {partIndex + 1}");
|
||||||
|
|
||||||
|
var part0 = span[ranges[0]];
|
||||||
|
var part1 = span[ranges[1]];
|
||||||
|
var part2 = span[ranges[2]];
|
||||||
|
var part3 = span[ranges[3]];
|
||||||
|
var part4 = span[ranges[4]];
|
||||||
|
|
||||||
|
// 解析 Enum 和 long
|
||||||
|
if (!Enum.TryParse(part0, out ChannelDevicePluginTypeEnum pluginType))
|
||||||
|
throw new FormatException($"Invalid {nameof(ChannelDevicePluginTypeEnum)}: {part0.ToString()}");
|
||||||
|
|
||||||
|
if (!long.TryParse(part1, out long deviceRuntimeId))
|
||||||
|
throw new FormatException($"Invalid DeviceRuntimeId: {part1.ToString()}");
|
||||||
|
|
||||||
|
if (!long.TryParse(part2, out long channelRuntimeId))
|
||||||
|
throw new FormatException($"Invalid ChannelRuntimeId: {part2.ToString()}");
|
||||||
|
|
||||||
|
string pluginName = part3.ToString();
|
||||||
|
|
||||||
|
PluginTypeEnum? parsedPluginType = null;
|
||||||
|
if (!part4.IsEmpty)
|
||||||
|
{
|
||||||
|
if (!Enum.TryParse(part4, out PluginTypeEnum tmp))
|
||||||
|
throw new FormatException($"Invalid {nameof(PluginTypeEnum)}: {part4.ToString()}");
|
||||||
|
parsedPluginType = tmp;
|
||||||
|
}
|
||||||
|
|
||||||
|
return new ChannelDeviceTreeItemStruct
|
||||||
|
{
|
||||||
|
ChannelDevicePluginType = pluginType,
|
||||||
|
DeviceRuntimeId = deviceRuntimeId,
|
||||||
|
ChannelRuntimeId = channelRuntimeId,
|
||||||
|
PluginName = pluginName,
|
||||||
|
PluginType = parsedPluginType
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,18 +0,0 @@
|
|||||||
//------------------------------------------------------------------------------
|
|
||||||
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
|
|
||||||
// 此代码版权(除特别声明外的代码)归作者本人Diego所有
|
|
||||||
// 源代码使用协议遵循本仓库的开源协议及附加协议
|
|
||||||
// Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway
|
|
||||||
// Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway
|
|
||||||
// 使用文档:https://thingsgateway.cn/
|
|
||||||
// QQ群:605534569
|
|
||||||
//------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
namespace ThingsGateway.Gateway.Razor;
|
|
||||||
|
|
||||||
public readonly struct CellValue
|
|
||||||
{
|
|
||||||
public readonly string Field { get; }
|
|
||||||
public readonly string Value { get; }
|
|
||||||
public CellValue(string f, string v) { Field = f; Value = v; }
|
|
||||||
}
|
|
||||||
@@ -1,12 +1,17 @@
|
|||||||
using ThingsGateway.NewLife.Caching;
|
using Microsoft.CSharp.RuntimeBinder;
|
||||||
using ThingsGateway.NewLife.Json.Extension;
|
|
||||||
|
|
||||||
|
using System.Dynamic;
|
||||||
|
using System.Linq.Expressions;
|
||||||
|
|
||||||
|
using ThingsGateway.Common.Extension;
|
||||||
|
using ThingsGateway.NewLife.Caching;
|
||||||
|
using ThingsGateway.NewLife.Json.Extension;
|
||||||
namespace ThingsGateway.Gateway.Razor;
|
namespace ThingsGateway.Gateway.Razor;
|
||||||
|
|
||||||
public static class VariableModelUtils
|
public static class VariableModelUtils
|
||||||
{
|
{
|
||||||
static MemoryCache MemoryCache = new();
|
static MemoryCache MemoryCache = new();
|
||||||
public static object GetPropertyValue(VariableRuntime model, string fieldName)
|
private static object GetPropertyValue(VariableRuntime model, string fieldName)
|
||||||
{
|
{
|
||||||
if (model == null)
|
if (model == null)
|
||||||
{
|
{
|
||||||
@@ -21,37 +26,100 @@ public static class VariableModelUtils
|
|||||||
{
|
{
|
||||||
var ret = MemoryCache.GetOrAdd(fieldName, (fieldName) =>
|
var ret = MemoryCache.GetOrAdd(fieldName, (fieldName) =>
|
||||||
{
|
{
|
||||||
return LambdaExtensions.GetPropertyValueLambda<VariableRuntime, object?>(model, fieldName).Compile();
|
return GetPropertyValueLambda<VariableRuntime, object?>(fieldName).Compile();
|
||||||
})(model);
|
})(model);
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 获取属性方法 Lambda 表达式
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="TModel"></typeparam>
|
||||||
|
/// <typeparam name="TResult"></typeparam>
|
||||||
|
/// <param name="propertyName"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
private static Expression<Func<TModel, TResult>> GetPropertyValueLambda<TModel, TResult>(string propertyName) where TModel : class, new()
|
||||||
|
{
|
||||||
|
|
||||||
|
var type = typeof(TModel);
|
||||||
|
var parameter = Expression.Parameter(typeof(TModel));
|
||||||
|
|
||||||
|
return !type.Assembly.IsDynamic && propertyName.Contains('.')
|
||||||
|
? GetComplexPropertyExpression()
|
||||||
|
: GetSimplePropertyExpression();
|
||||||
|
|
||||||
|
Expression<Func<TModel, TResult>> GetSimplePropertyExpression()
|
||||||
|
{
|
||||||
|
Expression body;
|
||||||
|
var p = type.GetPropertyByName(propertyName);
|
||||||
|
if (p != null)
|
||||||
|
{
|
||||||
|
body = Expression.Property(Expression.Convert(parameter, type), p);
|
||||||
|
}
|
||||||
|
else if (type.IsAssignableTo(typeof(IDynamicMetaObjectProvider)))
|
||||||
|
{
|
||||||
|
var binder = Microsoft.CSharp.RuntimeBinder.Binder.GetMember(
|
||||||
|
CSharpBinderFlags.None,
|
||||||
|
propertyName,
|
||||||
|
type,
|
||||||
|
[CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.None, null)]);
|
||||||
|
body = Expression.Dynamic(binder, typeof(object), parameter);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException($"类型 {type.Name} 未找到 {propertyName} 属性,无法获取其值");
|
||||||
|
}
|
||||||
|
|
||||||
|
return Expression.Lambda<Func<TModel, TResult>>(Expression.Convert(body, typeof(TResult)), parameter);
|
||||||
|
}
|
||||||
|
|
||||||
|
Expression<Func<TModel, TResult>> GetComplexPropertyExpression()
|
||||||
|
{
|
||||||
|
var propertyNames = propertyName.Split(".");
|
||||||
|
Expression? body = null;
|
||||||
|
Type t = type;
|
||||||
|
object? propertyInstance = new TModel();
|
||||||
|
foreach (var name in propertyNames)
|
||||||
|
{
|
||||||
|
var p = t.GetPropertyByName(name) ?? throw new InvalidOperationException($"类型 {type.Name} 未找到 {name} 属性,无法获取其值");
|
||||||
|
propertyInstance = p.GetValue(propertyInstance);
|
||||||
|
if (propertyInstance != null)
|
||||||
|
{
|
||||||
|
t = propertyInstance.GetType();
|
||||||
|
}
|
||||||
|
|
||||||
|
body = Expression.Property(body ?? Expression.Convert(parameter, type), p);
|
||||||
|
}
|
||||||
|
return Expression.Lambda<Func<TModel, TResult>>(Expression.Convert(body!, typeof(TResult)), parameter);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public static string GetValue(VariableRuntime row, string fieldName)
|
public static string GetValue(VariableRuntime row, string fieldName)
|
||||||
{
|
{
|
||||||
switch (fieldName)
|
switch (fieldName)
|
||||||
{
|
{
|
||||||
case nameof(VariableRuntime.Value):
|
case nameof(VariableRuntime.Value):
|
||||||
return row.Value?.ToSystemTextJsonString() ?? string.Empty;
|
return row.Value?.ToSystemTextJsonString(false) ?? string.Empty;
|
||||||
case nameof(VariableRuntime.RawValue):
|
case nameof(VariableRuntime.RawValue):
|
||||||
return row.RawValue?.ToSystemTextJsonString() ?? string.Empty;
|
return row.RawValue?.ToSystemTextJsonString(false) ?? string.Empty;
|
||||||
case nameof(VariableRuntime.LastSetValue):
|
case nameof(VariableRuntime.LastSetValue):
|
||||||
return row.LastSetValue?.ToSystemTextJsonString() ?? string.Empty;
|
return row.LastSetValue?.ToSystemTextJsonString(false) ?? string.Empty;
|
||||||
case nameof(VariableRuntime.ChangeTime):
|
case nameof(VariableRuntime.ChangeTime):
|
||||||
return row.ChangeTime.ToString("dd-HH:mm:ss.fff");
|
return row.ChangeTime.ToString("MM-dd HH:mm:ss.fff");
|
||||||
|
|
||||||
case nameof(VariableRuntime.CollectTime):
|
case nameof(VariableRuntime.CollectTime):
|
||||||
return row.CollectTime.ToString("dd-HH:mm:ss.fff");
|
return row.CollectTime.ToString("MM-dd HH:mm:ss.fff");
|
||||||
|
|
||||||
case nameof(VariableRuntime.IsOnline):
|
case nameof(VariableRuntime.IsOnline):
|
||||||
return row.IsOnline.ToString();
|
return row.IsOnline ? "Online" : "Offline";
|
||||||
|
|
||||||
case nameof(VariableRuntime.LastErrorMessage):
|
case nameof(VariableRuntime.LastErrorMessage):
|
||||||
return row.LastErrorMessage;
|
return row.LastErrorMessage;
|
||||||
|
|
||||||
|
|
||||||
case nameof(VariableRuntime.RuntimeType):
|
case nameof(VariableRuntime.RuntimeType):
|
||||||
return row.RuntimeType;
|
return row.RuntimeType;
|
||||||
default:
|
default:
|
||||||
|
|
||||||
var ret = VariableModelUtils.GetPropertyValue(row, fieldName);
|
var ret = VariableModelUtils.GetPropertyValue(row, fieldName);
|
||||||
@@ -69,7 +137,7 @@ public static class VariableModelUtils
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return ret is string str ? str : ret?.ToString() ?? string.Empty;
|
return ret is string str ? str : ret?.ToString() ?? string.Empty;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,26 +0,0 @@
|
|||||||
@namespace ThingsGateway.Gateway.Razor
|
|
||||||
@using System.Text.Json.Nodes
|
|
||||||
@using Microsoft.Extensions.Hosting
|
|
||||||
@using ThingsGateway.Admin.Application
|
|
||||||
@using ThingsGateway.Admin.Razor
|
|
||||||
@using ThingsGateway.Gateway.Application
|
|
||||||
@inherits ComponentDefault
|
|
||||||
|
|
||||||
|
|
||||||
@foreach (var col in RowContent.Columns)
|
|
||||||
{
|
|
||||||
<td class="@GetFixedCellClassString(col)" style="@GetFixedCellStyleString(col)" @key=col>
|
|
||||||
<div class="@GetCellClassString(col, false, false)" @key=col>
|
|
||||||
@if(col.GetShowTips())
|
|
||||||
{
|
|
||||||
<Tooltip @key=col Title="@VariableModelUtils.GetValue(RowContent.Row, col.GetFieldName())" class="text-truncate d-block">
|
|
||||||
@VariableModelUtils.GetValue(RowContent.Row, col.GetFieldName())
|
|
||||||
</Tooltip>
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
@VariableModelUtils.GetValue(RowContent.Row, col.GetFieldName())
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
}
|
|
||||||
@@ -1,249 +0,0 @@
|
|||||||
//------------------------------------------------------------------------------
|
|
||||||
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
|
|
||||||
// 此代码版权(除特别声明外的代码)归作者本人Diego所有
|
|
||||||
// 源代码使用协议遵循本仓库的开源协议及附加协议
|
|
||||||
// Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway
|
|
||||||
// Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway
|
|
||||||
// 使用文档:https://thingsgateway.cn/
|
|
||||||
// QQ群:605534569
|
|
||||||
//------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
using System.Collections.Concurrent;
|
|
||||||
|
|
||||||
namespace ThingsGateway.Gateway.Razor;
|
|
||||||
|
|
||||||
public partial class VariableRow
|
|
||||||
{
|
|
||||||
[Parameter]
|
|
||||||
public TableRowContext<VariableRuntime>? RowContent { get; set; }
|
|
||||||
//private bool Disposed;
|
|
||||||
//public void Dispose()
|
|
||||||
//{
|
|
||||||
// Disposed = true;
|
|
||||||
// timer?.SafeDispose();
|
|
||||||
// GC.SuppressFinalize(this);
|
|
||||||
//}
|
|
||||||
//TimerX? timer;
|
|
||||||
//protected override void OnAfterRender(bool firstRender)
|
|
||||||
//{
|
|
||||||
// if (firstRender)
|
|
||||||
// {
|
|
||||||
// timer = new TimerX(Refresh, null, 1000, 1000, "VariableRow");
|
|
||||||
// }
|
|
||||||
// base.OnAfterRender(firstRender);
|
|
||||||
//}
|
|
||||||
|
|
||||||
//private Task Refresh(object? state)
|
|
||||||
//{
|
|
||||||
// if (!Disposed)
|
|
||||||
// return InvokeAsync(StateHasChanged);
|
|
||||||
// else
|
|
||||||
// return Task.CompletedTask;
|
|
||||||
//}
|
|
||||||
|
|
||||||
protected override void OnParametersSet()
|
|
||||||
{
|
|
||||||
FixedCellClassStringCache?.Clear();
|
|
||||||
CellClassStringCache?.Clear();
|
|
||||||
base.OnParametersSet();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 获得指定列头固定列样式
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="col"></param>
|
|
||||||
/// <param name="margin"></param>
|
|
||||||
/// <returns></returns>
|
|
||||||
protected string? GetFixedCellStyleString(ITableColumn col, int margin = 0)
|
|
||||||
{
|
|
||||||
string? ret = null;
|
|
||||||
if (col.Fixed)
|
|
||||||
{
|
|
||||||
ret = IsTail(col) ? GetRightStyle(col, margin) : GetLeftStyle(col);
|
|
||||||
}
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
private string? GetLeftStyle(ITableColumn col)
|
|
||||||
{
|
|
||||||
var columns = RowContent.Columns.ToList();
|
|
||||||
var defaultWidth = 200;
|
|
||||||
var width = 0;
|
|
||||||
var start = 0;
|
|
||||||
var index = columns.IndexOf(col);
|
|
||||||
//if (GetFixedDetailRowHeaderColumn)
|
|
||||||
//{
|
|
||||||
// width += DetailColumnWidth;
|
|
||||||
//}
|
|
||||||
//if (GetFixedMultipleSelectColumn)
|
|
||||||
//{
|
|
||||||
// width += MultiColumnWidth;
|
|
||||||
//}
|
|
||||||
if (GetFixedLineNoColumn)
|
|
||||||
{
|
|
||||||
width += LineNoColumnWidth;
|
|
||||||
}
|
|
||||||
while (index > start)
|
|
||||||
{
|
|
||||||
var column = columns[start++];
|
|
||||||
width += column.Width ?? defaultWidth;
|
|
||||||
}
|
|
||||||
return $"left: {width}px;";
|
|
||||||
}
|
|
||||||
private bool GetFixedLineNoColumn = false;
|
|
||||||
|
|
||||||
private string? GetRightStyle(ITableColumn col, int margin)
|
|
||||||
{
|
|
||||||
var columns = RowContent.Columns.ToList();
|
|
||||||
var defaultWidth = 200;
|
|
||||||
var width = 0;
|
|
||||||
var index = columns.IndexOf(col);
|
|
||||||
|
|
||||||
// after
|
|
||||||
while (index + 1 < columns.Count)
|
|
||||||
{
|
|
||||||
var column = columns[index++];
|
|
||||||
width += column.Width ?? defaultWidth;
|
|
||||||
}
|
|
||||||
//if (ShowExtendButtons && FixedExtendButtonsColumn)
|
|
||||||
{
|
|
||||||
width += ExtendButtonColumnWidth;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 如果是固定表头时增加滚动条位置
|
|
||||||
if (IsFixedHeader && (index + 1) == columns.Count)
|
|
||||||
{
|
|
||||||
width += margin;
|
|
||||||
}
|
|
||||||
return $"right: {width}px;";
|
|
||||||
}
|
|
||||||
private bool IsFixedHeader = true;
|
|
||||||
|
|
||||||
public int LineNoColumnWidth { get; set; } = 60;
|
|
||||||
public int ExtendButtonColumnWidth { get; set; } = 220;
|
|
||||||
|
|
||||||
|
|
||||||
private bool IsTail(ITableColumn col)
|
|
||||||
{
|
|
||||||
var middle = Math.Floor(RowContent.Columns.Count() * 1.0 / 2);
|
|
||||||
var index = Columns.IndexOf(col);
|
|
||||||
return middle < index;
|
|
||||||
}
|
|
||||||
private NonBlockingDictionary<ITableColumn, string> CellClassStringCache { get; } = new(ReferenceEqualityComparer.Instance);
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 获得 Cell 文字样式
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="col"></param>
|
|
||||||
/// <param name="hasChildren"></param>
|
|
||||||
/// <param name="inCell"></param>
|
|
||||||
/// <returns></returns>
|
|
||||||
protected string? GetCellClassString(ITableColumn col, bool hasChildren, bool inCell)
|
|
||||||
{
|
|
||||||
if (CellClassStringCache.TryGetValue(col, out var cached))
|
|
||||||
{
|
|
||||||
return cached;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
bool trigger = false;
|
|
||||||
return CellClassStringCache.GetOrAdd(col, col => CssBuilder.Default("table-cell")
|
|
||||||
.AddClass(col.GetAlign().ToDescriptionString(), col.Align == Alignment.Center || col.Align == Alignment.Right)
|
|
||||||
.AddClass("is-wrap", col.GetTextWrap())
|
|
||||||
.AddClass("is-ellips", col.GetTextEllipsis())
|
|
||||||
.AddClass("is-tips", col.GetShowTips())
|
|
||||||
.AddClass("is-resizable", AllowResizing)
|
|
||||||
.AddClass("is-tree", IsTree && hasChildren)
|
|
||||||
.AddClass("is-incell", inCell)
|
|
||||||
.AddClass("is-dbcell", trigger)
|
|
||||||
.AddClass(col.CssClass)
|
|
||||||
.Build());
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
private bool AllowResizing = true;
|
|
||||||
private bool IsTree = false;
|
|
||||||
|
|
||||||
private NonBlockingDictionary<ITableColumn, string> FixedCellClassStringCache { get; } = new(ReferenceEqualityComparer.Instance);
|
|
||||||
/// <summary>
|
|
||||||
/// 获得指定列头固定列样式
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="col"></param>
|
|
||||||
/// <returns></returns>
|
|
||||||
protected string? GetFixedCellClassString(ITableColumn col)
|
|
||||||
{
|
|
||||||
if (FixedCellClassStringCache.TryGetValue(col, out var cached))
|
|
||||||
{
|
|
||||||
return cached;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
return FixedCellClassStringCache.GetOrAdd(col, col => CssBuilder.Default(col.GetFieldName())
|
|
||||||
.AddClass("fixed", col.Fixed)
|
|
||||||
.AddClass("fixed-right", col.Fixed && IsTail(col))
|
|
||||||
.AddClass("fr", IsLastColumn(col))
|
|
||||||
.AddClass("fl", IsFirstColumn(col))
|
|
||||||
.Build());
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
[Parameter]
|
|
||||||
public Func<List<ITableColumn>> ColumnsFunc { get; set; }
|
|
||||||
public List<ITableColumn> Columns => ColumnsFunc();
|
|
||||||
|
|
||||||
private NonBlockingDictionary<ITableColumn, bool> LastFixedColumnCache { get; } = new(ReferenceEqualityComparer.Instance);
|
|
||||||
private bool IsLastColumn(ITableColumn col)
|
|
||||||
{
|
|
||||||
if (LastFixedColumnCache.TryGetValue(col, out var cached))
|
|
||||||
{
|
|
||||||
return cached;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
return LastFixedColumnCache.GetOrAdd(col, col =>
|
|
||||||
{
|
|
||||||
var ret = false;
|
|
||||||
if (col.Fixed && !IsTail(col))
|
|
||||||
{
|
|
||||||
var index = Columns.IndexOf(col) + 1;
|
|
||||||
ret = index < Columns.Count && Columns[index].Fixed == false;
|
|
||||||
}
|
|
||||||
return ret;
|
|
||||||
});
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
private NonBlockingDictionary<ITableColumn, bool> FirstFixedColumnCache { get; } = new(ReferenceEqualityComparer.Instance);
|
|
||||||
private bool IsFirstColumn(ITableColumn col)
|
|
||||||
{
|
|
||||||
if (FirstFixedColumnCache.TryGetValue(col, out var cached))
|
|
||||||
{
|
|
||||||
return cached;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
return FirstFixedColumnCache.GetOrAdd(col, col =>
|
|
||||||
{
|
|
||||||
var ret = false;
|
|
||||||
if (col.Fixed && IsTail(col))
|
|
||||||
{
|
|
||||||
// 查找前一列是否固定
|
|
||||||
var index = Columns.IndexOf(col) - 1;
|
|
||||||
if (index > 0)
|
|
||||||
{
|
|
||||||
ret = !Columns[index].Fixed;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return ret;
|
|
||||||
});
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -47,7 +47,7 @@
|
|||||||
|
|
||||||
<TableColumn @bind-Field="@context.Enable" Filterable=true Sortable=true Visible="false" />
|
<TableColumn @bind-Field="@context.Enable" Filterable=true Sortable=true Visible="false" />
|
||||||
<TableColumn Field="@context.ChangeTime" Width=120 ShowTips=true FieldExpression=@(() => context.ChangeTime) Filterable=true Sortable=true Visible=false />
|
<TableColumn Field="@context.ChangeTime" Width=120 ShowTips=true FieldExpression=@(() => context.ChangeTime) Filterable=true Sortable=true Visible=false />
|
||||||
<TableColumn Field="@context.CollectTime" Width=120 ShowTips =true FieldExpression=@(() => context.CollectTime) Filterable=true Sortable=true Visible=true />
|
<TableColumn Field="@context.CollectTime" Width=120 ShowTips=true FieldExpression=@(() => context.CollectTime) Filterable=true Sortable=true Visible=true />
|
||||||
<TableColumn Field="@context.IsOnline" FieldExpression=@(() => context.IsOnline) Filterable=true Sortable=true Visible=true />
|
<TableColumn Field="@context.IsOnline" FieldExpression=@(() => context.IsOnline) Filterable=true Sortable=true Visible=true />
|
||||||
<TableColumn Field="@context.LastErrorMessage" ShowTips=true FieldExpression=@(() => context.LastErrorMessage) Filterable=true Sortable=true Visible=false />
|
<TableColumn Field="@context.LastErrorMessage" ShowTips=true FieldExpression=@(() => context.LastErrorMessage) Filterable=true Sortable=true Visible=false />
|
||||||
|
|
||||||
@@ -93,7 +93,28 @@
|
|||||||
|
|
||||||
<RowContentTemplate Context="context">
|
<RowContentTemplate Context="context">
|
||||||
|
|
||||||
<VariableRow RowContent="@context" ColumnsFunc="ColumnsFunc"></VariableRow>
|
|
||||||
|
@foreach (var col in context.Columns)
|
||||||
|
{
|
||||||
|
<td class="@GetFixedCellClassString(col, context)" style="@GetFixedCellStyleString(col, context)" @key=col>
|
||||||
|
@{
|
||||||
|
var name = col.GetFieldName();
|
||||||
|
var data = VariableModelUtils.GetValue(context.Row, name);
|
||||||
|
}
|
||||||
|
<div class="@GetCellClassString(col, data, false, false)" @key=col>
|
||||||
|
@if (col.GetShowTips())
|
||||||
|
{
|
||||||
|
<Tooltip @key=col Title="@VariableModelUtils.GetValue(context.Row, name)" class="text-truncate d-block">
|
||||||
|
@data
|
||||||
|
</Tooltip>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
@data
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
}
|
||||||
|
|
||||||
</RowContentTemplate>
|
</RowContentTemplate>
|
||||||
|
|
||||||
|
|||||||
@@ -60,11 +60,160 @@ public partial class VariableRuntimeInfo
|
|||||||
[NotNull]
|
[NotNull]
|
||||||
public ToastService? ToastService { get; set; }
|
public ToastService? ToastService { get; set; }
|
||||||
|
|
||||||
#region js
|
#region row
|
||||||
private List<ITableColumn> ColumnsFunc()
|
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 获得指定列头固定列样式
|
||||||
|
/// </summary>
|
||||||
|
protected string? GetFixedCellStyleString(ITableColumn col, TableRowContext<VariableRuntime> row, int margin = 0)
|
||||||
{
|
{
|
||||||
return table.Columns;
|
string? ret = null;
|
||||||
|
if (col.Fixed)
|
||||||
|
{
|
||||||
|
ret = IsTail(col, row) ? GetRightStyle(col, row, margin) : GetLeftStyle(col, row);
|
||||||
|
}
|
||||||
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private string? GetLeftStyle(ITableColumn col, TableRowContext<VariableRuntime> row)
|
||||||
|
{
|
||||||
|
var columns = row.Columns.ToList();
|
||||||
|
var defaultWidth = 200;
|
||||||
|
var width = 0;
|
||||||
|
var start = 0;
|
||||||
|
var index = columns.IndexOf(col);
|
||||||
|
//if (GetFixedDetailRowHeaderColumn)
|
||||||
|
//{
|
||||||
|
// width += DetailColumnWidth;
|
||||||
|
//}
|
||||||
|
//if (GetFixedMultipleSelectColumn)
|
||||||
|
//{
|
||||||
|
// width += MultiColumnWidth;
|
||||||
|
//}
|
||||||
|
if (GetFixedLineNoColumn)
|
||||||
|
{
|
||||||
|
width += LineNoColumnWidth;
|
||||||
|
}
|
||||||
|
while (index > start)
|
||||||
|
{
|
||||||
|
var column = columns[start++];
|
||||||
|
width += column.Width ?? defaultWidth;
|
||||||
|
}
|
||||||
|
return $"left: {width}px;";
|
||||||
|
}
|
||||||
|
private bool GetFixedLineNoColumn = false;
|
||||||
|
|
||||||
|
private string? GetRightStyle(ITableColumn col, TableRowContext<VariableRuntime> row, int margin)
|
||||||
|
{
|
||||||
|
var columns = row.Columns.ToList();
|
||||||
|
var defaultWidth = 200;
|
||||||
|
var width = 0;
|
||||||
|
var index = columns.IndexOf(col);
|
||||||
|
|
||||||
|
// after
|
||||||
|
while (index + 1 < columns.Count)
|
||||||
|
{
|
||||||
|
var column = columns[index++];
|
||||||
|
width += column.Width ?? defaultWidth;
|
||||||
|
}
|
||||||
|
//if (ShowExtendButtons && FixedExtendButtonsColumn)
|
||||||
|
{
|
||||||
|
width += ExtendButtonColumnWidth;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果是固定表头时增加滚动条位置
|
||||||
|
if (IsFixedHeader && (index + 1) == columns.Count)
|
||||||
|
{
|
||||||
|
width += margin;
|
||||||
|
}
|
||||||
|
return $"right: {width}px;";
|
||||||
|
}
|
||||||
|
private bool IsFixedHeader = true;
|
||||||
|
|
||||||
|
public int LineNoColumnWidth { get; set; } = 60;
|
||||||
|
public int ExtendButtonColumnWidth { get; set; } = 220;
|
||||||
|
|
||||||
|
|
||||||
|
private bool IsTail(ITableColumn col, TableRowContext<VariableRuntime> row)
|
||||||
|
{
|
||||||
|
var middle = Math.Floor(row.Columns.Count() * 1.0 / 2);
|
||||||
|
var index = Columns.IndexOf(col);
|
||||||
|
return middle < index;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 获得 Cell 文字样式
|
||||||
|
/// </summary>
|
||||||
|
protected string? GetCellClassString(ITableColumn col, string data, bool hasChildren, bool inCell)
|
||||||
|
{
|
||||||
|
bool trigger = false;
|
||||||
|
return CssBuilder.Default("table-cell")
|
||||||
|
.AddClass(col.GetAlign().ToDescriptionString(), col.Align == Alignment.Center || col.Align == Alignment.Right)
|
||||||
|
.AddClass("green--text", data == "Online")
|
||||||
|
.AddClass("red--text", data == "Offline")
|
||||||
|
.AddClass("is-wrap", col.GetTextWrap())
|
||||||
|
.AddClass("is-ellips", col.GetTextEllipsis())
|
||||||
|
.AddClass("is-tips", col.GetShowTips())
|
||||||
|
.AddClass("is-resizable", AllowResizing)
|
||||||
|
.AddClass("is-tree", IsTree && hasChildren)
|
||||||
|
.AddClass("is-incell", inCell)
|
||||||
|
.AddClass("is-dbcell", trigger)
|
||||||
|
.AddClass(col.CssClass)
|
||||||
|
.Build();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool AllowResizing = true;
|
||||||
|
private bool IsTree = false;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 获得指定列头固定列样式
|
||||||
|
/// </summary>
|
||||||
|
protected string? GetFixedCellClassString(ITableColumn col, TableRowContext<VariableRuntime> row)
|
||||||
|
{
|
||||||
|
return CssBuilder.Default()
|
||||||
|
.AddClass("fixed", col.Fixed)
|
||||||
|
.AddClass("fixed-right", col.Fixed && IsTail(col, row))
|
||||||
|
.AddClass("fr", IsLastColumn(col, row))
|
||||||
|
.AddClass("fl", IsFirstColumn(col, row))
|
||||||
|
.Build();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<ITableColumn> Columns => table?.Columns;
|
||||||
|
|
||||||
|
private bool IsLastColumn(ITableColumn col, TableRowContext<VariableRuntime> row)
|
||||||
|
{
|
||||||
|
var ret = false;
|
||||||
|
if (col.Fixed && !IsTail(col, row))
|
||||||
|
{
|
||||||
|
var index = Columns.IndexOf(col) + 1;
|
||||||
|
ret = index < Columns.Count && Columns[index].Fixed == false;
|
||||||
|
}
|
||||||
|
return ret;
|
||||||
|
|
||||||
|
}
|
||||||
|
private bool IsFirstColumn(ITableColumn col, TableRowContext<VariableRuntime> row)
|
||||||
|
{
|
||||||
|
|
||||||
|
var ret = false;
|
||||||
|
if (col.Fixed && IsTail(col, row))
|
||||||
|
{
|
||||||
|
// 查找前一列是否固定
|
||||||
|
var index = Columns.IndexOf(col) - 1;
|
||||||
|
if (index > 0)
|
||||||
|
{
|
||||||
|
ret = !Columns[index].Fixed;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region js
|
||||||
|
|
||||||
protected override Task InvokeInitAsync() => InvokeVoidAsync("init", Id, Interop, new { Method = nameof(TriggerStateChanged) });
|
protected override Task InvokeInitAsync() => InvokeVoidAsync("init", Id, Interop, new { Method = nameof(TriggerStateChanged) });
|
||||||
|
|
||||||
private Task OnColumnVisibleChanged(string name, bool visible)
|
private Task OnColumnVisibleChanged(string name, bool visible)
|
||||||
@@ -87,22 +236,25 @@ public partial class VariableRuntimeInfo
|
|||||||
private ITableColumn[] _cachedFields = Array.Empty<ITableColumn>();
|
private ITableColumn[] _cachedFields = Array.Empty<ITableColumn>();
|
||||||
|
|
||||||
[JSInvokable]
|
[JSInvokable]
|
||||||
public List<CellValue> TriggerStateChanged(int rowIndex)
|
public List<List<string>> TriggerStateChanged()
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
|
||||||
if (table == null) return null;
|
if (table == null) return null;
|
||||||
var row = table.Rows[rowIndex];
|
List<List<string>> ret = new();
|
||||||
if (_cachedFields.Length == 0) _cachedFields = table.GetVisibleColumns.ToArray();
|
if (_cachedFields.Length == 0) _cachedFields = table.GetVisibleColumns.ToArray();
|
||||||
var list = new List<CellValue>(_cachedFields.Length);
|
foreach (var row in table.Rows)
|
||||||
foreach (var col in _cachedFields)
|
|
||||||
{
|
{
|
||||||
var fieldName = col.GetFieldName();
|
var list = new List<string>(_cachedFields.Length);
|
||||||
list.Add(new(fieldName, VariableModelUtils.GetValue(row,fieldName)));
|
foreach (var col in _cachedFields)
|
||||||
|
{
|
||||||
|
var fieldName = col.GetFieldName();
|
||||||
|
list.Add(VariableModelUtils.GetValue(row, fieldName));
|
||||||
|
}
|
||||||
|
ret.Add(list);
|
||||||
}
|
}
|
||||||
|
|
||||||
return list;
|
return ret;
|
||||||
|
|
||||||
}
|
}
|
||||||
catch (Exception)
|
catch (Exception)
|
||||||
@@ -140,11 +292,16 @@ public partial class VariableRuntimeInfo
|
|||||||
[Inject]
|
[Inject]
|
||||||
private IOptions<WebsiteOptions>? WebsiteOption { get; set; }
|
private IOptions<WebsiteOptions>? WebsiteOption { get; set; }
|
||||||
public bool Disposed { get; set; }
|
public bool Disposed { get; set; }
|
||||||
protected override ValueTask DisposeAsync(bool disposing)
|
protected override async ValueTask DisposeAsync(bool disposing)
|
||||||
{
|
{
|
||||||
|
|
||||||
Disposed = true;
|
Disposed = true;
|
||||||
VariableRuntimeDispatchService.UnSubscribe(Refresh);
|
VariableRuntimeDispatchService?.UnSubscribe(Refresh);
|
||||||
return base.DisposeAsync(disposing);
|
|
||||||
|
if (Module != null)
|
||||||
|
await Module.InvokeVoidAsync("dispose", Id);
|
||||||
|
|
||||||
|
await base.DisposeAsync(disposing);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void OnInitialized()
|
protected override void OnInitialized()
|
||||||
|
|||||||
@@ -1,11 +1,16 @@
|
|||||||
export function init(id, invoke, options) {
|
let handlers = {};
|
||||||
function getCellByClass(row, className) {
|
|
||||||
// 直接用 querySelector 精确查找
|
|
||||||
return row.querySelector(`td.${className}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
export function init(id, invoke, options) {
|
||||||
|
//function getCellByClass(row, className) {
|
||||||
|
// // 直接用 querySelector 精确查找
|
||||||
|
// return row.querySelector(`td.${className}`);
|
||||||
|
//}
|
||||||
var variableHandler = setInterval(async () => {
|
var variableHandler = setInterval(async () => {
|
||||||
var admintable = document.getElementById(id);
|
var admintable = document.getElementById(id);
|
||||||
|
if (!admintable) {
|
||||||
|
clearInterval(variableHandler);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
var tables = admintable.getElementsByTagName('table');
|
var tables = admintable.getElementsByTagName('table');
|
||||||
|
|
||||||
@@ -16,27 +21,32 @@
|
|||||||
var table = tables[tables.length - 1];
|
var table = tables[tables.length - 1];
|
||||||
|
|
||||||
if (!table) {
|
if (!table) {
|
||||||
clearInterval(variableHandler)
|
clearInterval(variableHandler);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
var rowCount = table.rows.length;
|
|
||||||
var { method } = options;
|
var { method } = options;
|
||||||
|
|
||||||
for (let rowIndex = 0; rowIndex < rowCount; rowIndex++) {
|
if (!invoke) return;
|
||||||
|
var valss = await invoke.invokeMethodAsync(method);
|
||||||
|
if (valss == null) return;
|
||||||
|
for (let rowIndex = 0; rowIndex < valss.length; rowIndex++) {
|
||||||
|
|
||||||
|
const vals = valss[rowIndex];
|
||||||
|
if (vals == null) continue;
|
||||||
|
|
||||||
|
|
||||||
var row = table.rows[rowIndex];
|
var row = table.rows[rowIndex];
|
||||||
if (!row) continue;
|
if (!row) continue;
|
||||||
|
|
||||||
var vals = await invoke.invokeMethodAsync(method, rowIndex);
|
|
||||||
if (vals == null) continue;
|
|
||||||
|
|
||||||
for (let i = 0; i < vals.length; i++) {
|
for (let i = 0; i < vals.length; i++) {
|
||||||
const cellName = vals[i].field;
|
|
||||||
const cellValue = vals[i].value;
|
const cellValue = vals[i];
|
||||||
if (cellValue == null) continue;
|
if (cellValue == null) continue;
|
||||||
|
|
||||||
var cell = getCellByClass(row, cellName)
|
//var cell = getCellByClass(row, cellName)
|
||||||
|
var cell = row.cells[i + 2]
|
||||||
|
|
||||||
if (!cell) continue;
|
if (!cell) continue;
|
||||||
|
|
||||||
@@ -46,14 +56,34 @@
|
|||||||
var tooltipSpan = cell.querySelector('.bb-tooltip');
|
var tooltipSpan = cell.querySelector('.bb-tooltip');
|
||||||
if (tooltipSpan) {
|
if (tooltipSpan) {
|
||||||
|
|
||||||
tooltipSpan.innerText = cellValue ?? ''; // 更新显示文字
|
if (tooltipSpan.innerText != cellValue) {
|
||||||
tooltipSpan.setAttribute('data-bs-original-title', cellValue ?? ''); // 同步 tooltip 提示
|
|
||||||
|
tooltipSpan.innerText = cellValue ?? ''; // 更新显示文字
|
||||||
|
tooltipSpan.setAttribute('data-bs-original-title', cellValue ?? ''); // 同步 tooltip 提示
|
||||||
|
|
||||||
|
}
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
cellDiv.innerText = cellValue ?? '';
|
if (cellDiv.innerText != cellValue) {
|
||||||
|
|
||||||
|
cellDiv.innerText = cellValue ?? '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (cellValue == "Online") {
|
||||||
|
cellDiv.classList.remove('red--text');
|
||||||
|
cellDiv.classList.add('green--text');
|
||||||
|
}
|
||||||
|
else if (cellValue == "Offline") {
|
||||||
|
cellDiv.classList.remove('green--text');
|
||||||
|
cellDiv.classList.add('red--text');
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
cellDiv.classList.remove('red--text');
|
||||||
|
cellDiv.classList.remove('green--text');
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -81,14 +111,24 @@
|
|||||||
// continue;
|
// continue;
|
||||||
//}
|
//}
|
||||||
//// 默认情况(普通单元格)
|
//// 默认情况(普通单元格)
|
||||||
//getCellByClass(row, cellName).innerText = cellValue;
|
//cell.innerText = cellValue;
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
, 500) //1000ms刷新一次
|
, 500) //1000ms刷新一次
|
||||||
|
|
||||||
}
|
handlers[id] = { variableHandler, invoke };
|
||||||
|
|
||||||
|
}
|
||||||
|
export function dispose(id) {
|
||||||
|
const handler = handlers[id];
|
||||||
|
if (handler) {
|
||||||
|
clearInterval(handler.timer);
|
||||||
|
handler.invoke = null;
|
||||||
|
delete handlers[id];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -11,11 +11,6 @@
|
|||||||
using BenchmarkDotNet.Attributes;
|
using BenchmarkDotNet.Attributes;
|
||||||
using BenchmarkDotNet.Diagnosers;
|
using BenchmarkDotNet.Diagnosers;
|
||||||
|
|
||||||
using Longbow.Modbus;
|
|
||||||
using Longbow.TcpSocket;
|
|
||||||
|
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
|
||||||
|
|
||||||
using System.Net.Sockets;
|
using System.Net.Sockets;
|
||||||
|
|
||||||
using ThingsGateway.Foundation.Modbus;
|
using ThingsGateway.Foundation.Modbus;
|
||||||
@@ -33,14 +28,12 @@ namespace ThingsGateway.Foundation;
|
|||||||
[MemoryDiagnoser]
|
[MemoryDiagnoser]
|
||||||
public class ModbusBenchmark : IDisposable
|
public class ModbusBenchmark : IDisposable
|
||||||
{
|
{
|
||||||
public static int ClientCount = 1;
|
public static int ClientCount = 10;
|
||||||
public static int TaskNumberOfItems = 1;
|
public static int TaskNumberOfItems = 1;
|
||||||
public static int NumberOfItems = 10;
|
public static int NumberOfItems = 100;
|
||||||
|
|
||||||
private readonly List<IModbusClient> _lgbModbusClients = [];
|
|
||||||
private List<ModbusMaster> thingsgatewaymodbuss = new();
|
private List<ModbusMaster> thingsgatewaymodbuss = new();
|
||||||
private List<IModbusMaster> nmodbuss = new();
|
private List<IModbusMaster> nmodbuss = new();
|
||||||
//private List<ModbusTcpNet> modbusTcpNets = new();
|
|
||||||
private List<ModbusTcpMaster> modbusTcpMasters = new();
|
private List<ModbusTcpMaster> modbusTcpMasters = new();
|
||||||
|
|
||||||
[GlobalSetup]
|
[GlobalSetup]
|
||||||
@@ -74,15 +67,7 @@ public class ModbusBenchmark : IDisposable
|
|||||||
await nmodbus.ReadHoldingRegistersAsync(1, 0, 100);
|
await nmodbus.ReadHoldingRegistersAsync(1, 0, 100);
|
||||||
nmodbuss.Add(nmodbus);
|
nmodbuss.Add(nmodbus);
|
||||||
}
|
}
|
||||||
//for (int i = 0; i < ClientCount; i++)
|
|
||||||
//{
|
|
||||||
// ModbusTcpNet modbusTcpNet = new();
|
|
||||||
// modbusTcpNet.IpAddress = "127.0.0.1";
|
|
||||||
// modbusTcpNet.Port = 502;
|
|
||||||
// modbusTcpNet.ConnectServer();
|
|
||||||
// modbusTcpNet.ReadAsync("0", 100);
|
|
||||||
// modbusTcpNets.Add(modbusTcpNet);
|
|
||||||
//}
|
|
||||||
|
|
||||||
for (int i = 0; i < ClientCount; i++)
|
for (int i = 0; i < ClientCount; i++)
|
||||||
{
|
{
|
||||||
@@ -94,23 +79,6 @@ public class ModbusBenchmark : IDisposable
|
|||||||
modbusTcpMasters.Add(client);
|
modbusTcpMasters.Add(client);
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
|
||||||
var sc = new ServiceCollection();
|
|
||||||
sc.AddTcpSocketFactory();
|
|
||||||
sc.AddModbusFactory();
|
|
||||||
|
|
||||||
var provider = sc.BuildServiceProvider();
|
|
||||||
var factory = provider.GetRequiredService<IModbusFactory>();
|
|
||||||
|
|
||||||
for (int i = 0; i < ClientCount; i++)
|
|
||||||
{
|
|
||||||
var client = factory.GetOrCreateTcpMaster();
|
|
||||||
await client.ConnectAsync("127.0.0.1", 502);
|
|
||||||
await client.ReadHoldingRegistersAsync(0x01, 0x00, 10);
|
|
||||||
|
|
||||||
_lgbModbusClients.Add(client);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[Benchmark]
|
[Benchmark]
|
||||||
@@ -140,33 +108,6 @@ public class ModbusBenchmark : IDisposable
|
|||||||
await Task.WhenAll(tasks);
|
await Task.WhenAll(tasks);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Benchmark]
|
|
||||||
public async Task LongbowModbus()
|
|
||||||
{
|
|
||||||
List<Task> tasks = new List<Task>();
|
|
||||||
foreach (var client in _lgbModbusClients)
|
|
||||||
{
|
|
||||||
|
|
||||||
for (int i = 0; i < TaskNumberOfItems; i++)
|
|
||||||
{
|
|
||||||
tasks.Add(Task.Run(async () =>
|
|
||||||
{
|
|
||||||
for (int i = 0; i < NumberOfItems; i++)
|
|
||||||
{
|
|
||||||
using var cts = new CancellationTokenSource(3000);
|
|
||||||
var result = await client.ReadHoldingRegistersAsync(1, 0, 100, cts.Token).ConfigureAwait(false);
|
|
||||||
var data = result.ReadUShortValues(100);
|
|
||||||
if (!result.IsSuccess)
|
|
||||||
{
|
|
||||||
throw new Exception(DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss ffff") + result.Exception);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
await Task.WhenAll(tasks);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Benchmark]
|
[Benchmark]
|
||||||
public async Task TouchSocket()
|
public async Task TouchSocket()
|
||||||
{
|
{
|
||||||
@@ -214,39 +155,12 @@ public class ModbusBenchmark : IDisposable
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
//并发失败
|
|
||||||
//[Benchmark]
|
|
||||||
//public async Task HslCommunication()
|
|
||||||
//{
|
|
||||||
// List<Task> tasks = new List<Task>();
|
|
||||||
// foreach (var modbusTcpNet in modbusTcpNets)
|
|
||||||
// {
|
|
||||||
// for (int i = 0; i < TaskNumberOfItems; i++)
|
|
||||||
// {
|
|
||||||
// tasks.Add(Task.Run(async () =>
|
|
||||||
// {
|
|
||||||
// for (int i = 0; i < NumberOfItems; i++)
|
|
||||||
// {
|
|
||||||
// var result = await modbusTcpNet.ReadAsync("0", 100);
|
|
||||||
// if (!result.IsSuccess)
|
|
||||||
// {
|
|
||||||
// throw new Exception(result.Message);
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }));
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// await Task.WhenAll(tasks);
|
|
||||||
//}
|
|
||||||
|
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
|
|
||||||
thingsgatewaymodbuss?.ForEach(a => a.Channel.SafeDispose());
|
thingsgatewaymodbuss?.ForEach(a => a.Channel.SafeDispose());
|
||||||
thingsgatewaymodbuss?.ForEach(a => a.SafeDispose());
|
thingsgatewaymodbuss?.ForEach(a => a.SafeDispose());
|
||||||
nmodbuss?.ForEach(a => a.SafeDispose());
|
nmodbuss?.ForEach(a => a.SafeDispose());
|
||||||
//modbusTcpNets?.ForEach(a => a.SafeDispose());
|
|
||||||
_lgbModbusClients?.ForEach(a => a.DisposeAsync());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -11,8 +11,6 @@
|
|||||||
using BenchmarkDotNet.Attributes;
|
using BenchmarkDotNet.Attributes;
|
||||||
using BenchmarkDotNet.Diagnosers;
|
using BenchmarkDotNet.Diagnosers;
|
||||||
|
|
||||||
using HslCommunication.Profinet.Siemens;
|
|
||||||
|
|
||||||
using S7.Net;
|
using S7.Net;
|
||||||
|
|
||||||
using ThingsGateway.Foundation.SiemensS7;
|
using ThingsGateway.Foundation.SiemensS7;
|
||||||
@@ -33,7 +31,6 @@ public class S7Benchmark : IDisposable
|
|||||||
private List<SiemensS7Master> siemensS7s = new();
|
private List<SiemensS7Master> siemensS7s = new();
|
||||||
|
|
||||||
private List<Plc> plcs = new();
|
private List<Plc> plcs = new();
|
||||||
private List<SiemensS7Net> siemensS7Nets = new();
|
|
||||||
|
|
||||||
[GlobalSetup]
|
[GlobalSetup]
|
||||||
public async Task Init()
|
public async Task Init()
|
||||||
@@ -57,13 +54,7 @@ public class S7Benchmark : IDisposable
|
|||||||
await siemensS7.ReadAsync("M1", 100);
|
await siemensS7.ReadAsync("M1", 100);
|
||||||
siemensS7s.Add(siemensS7);
|
siemensS7s.Add(siemensS7);
|
||||||
}
|
}
|
||||||
for (int i = 0; i < ClientCount; i++)
|
|
||||||
{
|
|
||||||
var siemensS7Net = new SiemensS7Net(SiemensPLCS.S1500, "127.0.0.1");
|
|
||||||
await siemensS7Net.ConnectServerAsync();
|
|
||||||
await siemensS7Net.ReadAsync("M0", 100);
|
|
||||||
siemensS7Nets.Add(siemensS7Net);
|
|
||||||
}
|
|
||||||
for (int i = 0; i < ClientCount; i++)
|
for (int i = 0; i < ClientCount; i++)
|
||||||
{
|
{
|
||||||
var plc = new Plc(CpuType.S71500, "127.0.0.1", 102, 0, 0);
|
var plc = new Plc(CpuType.S71500, "127.0.0.1", 102, 0, 0);
|
||||||
@@ -94,34 +85,6 @@ public class S7Benchmark : IDisposable
|
|||||||
await Task.WhenAll(tasks);
|
await Task.WhenAll(tasks);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Benchmark]
|
|
||||||
public async Task HslCommunication()
|
|
||||||
{
|
|
||||||
List<Task> tasks = new List<Task>();
|
|
||||||
foreach (var siemensS7Net in siemensS7Nets)
|
|
||||||
{
|
|
||||||
for (int i = 0; i < TaskNumberOfItems; i++)
|
|
||||||
{
|
|
||||||
tasks.Add(Task.Run(async () =>
|
|
||||||
{
|
|
||||||
for (int i = 0; i < NumberOfItems; i++)
|
|
||||||
{
|
|
||||||
var result = await siemensS7Net.ReadAsync("M0", 100);
|
|
||||||
if (!result.IsSuccess)
|
|
||||||
{
|
|
||||||
throw new Exception(result.Message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
await Task.WhenAll(tasks);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
[Benchmark]
|
[Benchmark]
|
||||||
public async Task ThingsGateway()
|
public async Task ThingsGateway()
|
||||||
@@ -151,7 +114,6 @@ public class S7Benchmark : IDisposable
|
|||||||
public void Dispose()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
plcs.ForEach(a => a.SafeDispose());
|
plcs.ForEach(a => a.SafeDispose());
|
||||||
siemensS7Nets.ForEach(a => a.SafeDispose());
|
|
||||||
siemensS7s.ForEach(a => a.Channel.SafeDispose());
|
siemensS7s.ForEach(a => a.Channel.SafeDispose());
|
||||||
siemensS7s.ForEach(a => a.SafeDispose());
|
siemensS7s.ForEach(a => a.SafeDispose());
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,94 @@
|
|||||||
|
//------------------------------------------------------------------------------
|
||||||
|
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
|
||||||
|
// 此代码版权(除特别声明外的代码)归作者本人Diego所有
|
||||||
|
// 源代码使用协议遵循本仓库的开源协议及附加协议
|
||||||
|
// Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway
|
||||||
|
// Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway
|
||||||
|
// 使用文档:https://kimdiego2098.github.io/
|
||||||
|
// QQ群:605534569
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
namespace ThingsGateway.Foundation;
|
||||||
|
|
||||||
|
using BenchmarkDotNet.Attributes;
|
||||||
|
|
||||||
|
using PooledAwait;
|
||||||
|
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
using ThingsGateway.NewLife;
|
||||||
|
|
||||||
|
[MemoryDiagnoser]
|
||||||
|
[ThreadingDiagnoser]
|
||||||
|
public class SemaphoreBenchmark
|
||||||
|
{
|
||||||
|
private readonly SemaphoreSlim _semaphoreSlim = new SemaphoreSlim(1);
|
||||||
|
private readonly WaitLock _waitLock = new WaitLock("SemaphoreBenchmark");
|
||||||
|
|
||||||
|
[Params(100)]
|
||||||
|
public int Iterations;
|
||||||
|
|
||||||
|
[Benchmark(Baseline = true)]
|
||||||
|
public async Task SemaphoreSlim_WaitRelease()
|
||||||
|
{
|
||||||
|
|
||||||
|
for (int i = 0; i < Iterations; i++)
|
||||||
|
{
|
||||||
|
await _semaphoreSlim.WaitAsync().ConfigureAwait(false);
|
||||||
|
|
||||||
|
await Task.Delay(1);
|
||||||
|
_semaphoreSlim.Release();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
[Benchmark]
|
||||||
|
public async Task ReusableAsyncSemaphore_WaitRelease()
|
||||||
|
{
|
||||||
|
for (int i = 0; i < Iterations; i++)
|
||||||
|
{
|
||||||
|
await _waitLock.WaitAsync().ConfigureAwait(false);
|
||||||
|
|
||||||
|
await Task.Delay(1);
|
||||||
|
_waitLock.Release();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
[Benchmark]
|
||||||
|
public async Task SemaphoreSlim_WaitReleaseToken()
|
||||||
|
{
|
||||||
|
|
||||||
|
for (int i = 0; i < Iterations; i++)
|
||||||
|
{
|
||||||
|
using var cts = new CancellationTokenSource(1000);
|
||||||
|
|
||||||
|
await _semaphoreSlim.WaitAsync(cts.Token).ConfigureAwait(false);
|
||||||
|
|
||||||
|
await Task.Delay(1);
|
||||||
|
_semaphoreSlim.Release();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
[Benchmark]
|
||||||
|
public ValueTask ReusableAsyncSemaphore_WaitReleaseToken()
|
||||||
|
{
|
||||||
|
return ReusableAsyncSemaphore_WaitReleaseToken(this);
|
||||||
|
|
||||||
|
static async PooledValueTask ReusableAsyncSemaphore_WaitReleaseToken(SemaphoreBenchmark @this)
|
||||||
|
{
|
||||||
|
for (int i = 0; i < @this.Iterations; i++)
|
||||||
|
{
|
||||||
|
using var cts = new CancellationTokenSource(1000);
|
||||||
|
|
||||||
|
await @this._waitLock.WaitAsync(cts.Token).ConfigureAwait(false);
|
||||||
|
|
||||||
|
await Task.Delay(1);
|
||||||
|
@this._waitLock.Release();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -11,6 +11,7 @@
|
|||||||
using BenchmarkDotNet.Attributes;
|
using BenchmarkDotNet.Attributes;
|
||||||
using BenchmarkDotNet.Diagnosers;
|
using BenchmarkDotNet.Diagnosers;
|
||||||
|
|
||||||
|
using ThingsGateway.NewLife;
|
||||||
using ThingsGateway.NewLife.Collections;
|
using ThingsGateway.NewLife.Collections;
|
||||||
|
|
||||||
namespace ThingsGateway.Foundation;
|
namespace ThingsGateway.Foundation;
|
||||||
@@ -35,11 +36,11 @@ public class TimeoutBenchmark
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private ObjectPool<ReusableCancellationTokenSource> _reusableTimeouts;
|
private ObjectPoolLock<ReusableCancellationTokenSource> _reusableTimeouts;
|
||||||
[Benchmark]
|
[Benchmark]
|
||||||
public async ValueTask ReusableTimeoutWaitAsync()
|
public async ValueTask ReusableTimeoutWaitAsync()
|
||||||
{
|
{
|
||||||
_reusableTimeouts ??= new();
|
_reusableTimeouts ??= new ObjectPoolLock<ReusableCancellationTokenSource>();
|
||||||
using var otherCts = new CancellationTokenSource();
|
using var otherCts = new CancellationTokenSource();
|
||||||
for (int i1 = 0; i1 < 10; i1++)
|
for (int i1 = 0; i1 < 10; i1++)
|
||||||
for (int i = 0; i < 10; i++)
|
for (int i = 0; i < 10; i++)
|
||||||
@@ -47,7 +48,7 @@ public class TimeoutBenchmark
|
|||||||
var _reusableTimeout = _reusableTimeouts.Get();
|
var _reusableTimeout = _reusableTimeouts.Get();
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
await Task.Delay(5, _reusableTimeout.GetTokenSource(10, otherCts.Token).Token).ConfigureAwait(false); // 模拟工作
|
await Task.Delay(5, _reusableTimeout.GetTokenSource(10, otherCts.Token)).ConfigureAwait(false); // 模拟工作
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -14,9 +14,7 @@
|
|||||||
using BenchmarkDotNet.Configs;
|
using BenchmarkDotNet.Configs;
|
||||||
using BenchmarkDotNet.Running;
|
using BenchmarkDotNet.Running;
|
||||||
|
|
||||||
using ThingsGateway.Foundation;
|
namespace ThingsGateway.Foundation
|
||||||
|
|
||||||
namespace BenchmarkConsoleApp
|
|
||||||
{
|
{
|
||||||
internal class Program
|
internal class Program
|
||||||
{
|
{
|
||||||
@@ -47,11 +45,18 @@ namespace BenchmarkConsoleApp
|
|||||||
//ManualConfig.Create(DefaultConfig.Instance)
|
//ManualConfig.Create(DefaultConfig.Instance)
|
||||||
//.WithOptions(ConfigOptions.DisableOptimizationsValidator)
|
//.WithOptions(ConfigOptions.DisableOptimizationsValidator)
|
||||||
//);
|
//);
|
||||||
|
// BenchmarkRunner.Run<BenchmarkAsyncWaitData>(
|
||||||
|
//ManualConfig.Create(DefaultConfig.Instance)
|
||||||
|
//.WithOptions(ConfigOptions.DisableOptimizationsValidator)
|
||||||
|
//);
|
||||||
|
// BenchmarkRunner.Run<SemaphoreBenchmark>(
|
||||||
|
// ManualConfig.Create(DefaultConfig.Instance)
|
||||||
|
// .WithOptions(ConfigOptions.DisableOptimizationsValidator)
|
||||||
|
//);
|
||||||
BenchmarkRunner.Run<ModbusBenchmark>(
|
BenchmarkRunner.Run<ModbusBenchmark>(
|
||||||
ManualConfig.Create(DefaultConfig.Instance)
|
ManualConfig.Create(DefaultConfig.Instance)
|
||||||
.WithOptions(ConfigOptions.DisableOptimizationsValidator)
|
.WithOptions(ConfigOptions.DisableOptimizationsValidator)
|
||||||
);
|
);
|
||||||
// BenchmarkRunner.Run<S7Benchmark>(
|
// BenchmarkRunner.Run<S7Benchmark>(
|
||||||
//ManualConfig.Create(DefaultConfig.Instance)
|
//ManualConfig.Create(DefaultConfig.Instance)
|
||||||
//.WithOptions(ConfigOptions.DisableOptimizationsValidator)
|
//.WithOptions(ConfigOptions.DisableOptimizationsValidator)
|
||||||
|
|||||||
@@ -42,10 +42,7 @@
|
|||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="BenchmarkDotNet" Version="0.15.4" />
|
<PackageReference Include="BenchmarkDotNet" Version="0.15.4" />
|
||||||
<PackageReference Include="HslCommunication" Version="12.5.1" />
|
|
||||||
<PackageReference Include="Longbow.Modbus" Version="9.1.1" />
|
|
||||||
<PackageReference Include="NModbus" Version="3.0.81" />
|
<PackageReference Include="NModbus" Version="3.0.81" />
|
||||||
<PackageReference Include="NModbus.Serial" Version="3.0.81" />
|
|
||||||
<PackageReference Include="S7netplus" Version="0.20.0" />
|
<PackageReference Include="S7netplus" Version="0.20.0" />
|
||||||
<!--<PackageReference Include="ThingsGateway.Foundation.Modbus" Version="$(DefaultVersion)" />
|
<!--<PackageReference Include="ThingsGateway.Foundation.Modbus" Version="$(DefaultVersion)" />
|
||||||
<PackageReference Include="ThingsGateway.Foundation.SiemensS7" Version="$(DefaultVersion)" />-->
|
<PackageReference Include="ThingsGateway.Foundation.SiemensS7" Version="$(DefaultVersion)" />-->
|
||||||
|
|||||||
@@ -1,14 +1,4 @@
|
|||||||
//------------------------------------------------------------------------------
|
namespace ThingsGateway.Foundation.Modbus;
|
||||||
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
|
|
||||||
// 此代码版权(除特别声明外的代码)归作者本人Diego所有
|
|
||||||
// 源代码使用协议遵循本仓库的开源协议及附加协议
|
|
||||||
// Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway
|
|
||||||
// Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway
|
|
||||||
// 使用文档:https://thingsgateway.cn/
|
|
||||||
// QQ群:605534569
|
|
||||||
//------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
namespace ThingsGateway.Foundation.Modbus;
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
|
|||||||
@@ -8,6 +8,8 @@
|
|||||||
// QQ群:605534569
|
// QQ群:605534569
|
||||||
//------------------------------------------------------------------------------
|
//------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
using PooledAwait;
|
||||||
|
|
||||||
using System.Buffers;
|
using System.Buffers;
|
||||||
using System.Collections.Concurrent;
|
using System.Collections.Concurrent;
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
@@ -27,22 +29,22 @@ public class ModbusSlave : DeviceBase, IModbusAddress
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// 继电器
|
/// 继电器
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private NonBlockingDictionary<byte, ByteBlock> ModbusServer01ByteBlocks = new();
|
private ConcurrentDictionary<int, ByteBlock> ModbusServer01ByteBlocks = new();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 开关输入
|
/// 开关输入
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private NonBlockingDictionary<byte, ByteBlock> ModbusServer02ByteBlocks = new();
|
private ConcurrentDictionary<int, ByteBlock> ModbusServer02ByteBlocks = new();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 输入寄存器
|
/// 输入寄存器
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private NonBlockingDictionary<byte, ByteBlock> ModbusServer03ByteBlocks = new();
|
private ConcurrentDictionary<int, ByteBlock> ModbusServer03ByteBlocks = new();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 保持寄存器
|
/// 保持寄存器
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private NonBlockingDictionary<byte, ByteBlock> ModbusServer04ByteBlocks = new();
|
private ConcurrentDictionary<int, ByteBlock> ModbusServer04ByteBlocks = new();
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public override void InitChannel(IChannel channel, ILog? deviceLog = null)
|
public override void InitChannel(IChannel channel, ILog? deviceLog = null)
|
||||||
@@ -187,8 +189,42 @@ public class ModbusSlave : DeviceBase, IModbusAddress
|
|||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
private void Init(ModbusRequest mAddress)
|
private void Init(ModbusRequest mAddress)
|
||||||
{
|
{
|
||||||
//自动扩容
|
if (ModbusServer01ByteBlocks.ContainsKey(mAddress.Station))
|
||||||
ModbusServer01ByteBlocks.GetOrAdd(mAddress.Station, a =>
|
return;
|
||||||
|
else
|
||||||
|
ModbusServer01ByteBlocks.GetOrAdd(mAddress.Station, a =>
|
||||||
|
{
|
||||||
|
var bytes = new ByteBlock(256,
|
||||||
|
(c) =>
|
||||||
|
{
|
||||||
|
var data = ArrayPool<byte>.Shared.Rent(c);
|
||||||
|
for (int i = 0; i < data.Length; i++)
|
||||||
|
{
|
||||||
|
data[i] = 0;
|
||||||
|
}
|
||||||
|
return data;
|
||||||
|
},
|
||||||
|
(m) =>
|
||||||
|
{
|
||||||
|
if (MemoryMarshal.TryGetArray((ReadOnlyMemory<byte>)m, out var result))
|
||||||
|
{
|
||||||
|
ArrayPool<byte>.Shared.Return(result.Array);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
bytes.SetLength(256);
|
||||||
|
for (int i = 0; i < bytes.Length; i++)
|
||||||
|
{
|
||||||
|
bytes.WriteByte(0);
|
||||||
|
}
|
||||||
|
bytes.Position = 0;
|
||||||
|
return bytes;
|
||||||
|
});
|
||||||
|
|
||||||
|
if (ModbusServer02ByteBlocks.ContainsKey(mAddress.Station))
|
||||||
|
return;
|
||||||
|
else
|
||||||
|
ModbusServer02ByteBlocks.GetOrAdd(mAddress.Station, a =>
|
||||||
{
|
{
|
||||||
var bytes = new ByteBlock(256,
|
var bytes = new ByteBlock(256,
|
||||||
(c) =>
|
(c) =>
|
||||||
@@ -216,7 +252,11 @@ public class ModbusSlave : DeviceBase, IModbusAddress
|
|||||||
bytes.Position = 0;
|
bytes.Position = 0;
|
||||||
return bytes;
|
return bytes;
|
||||||
});
|
});
|
||||||
ModbusServer02ByteBlocks.GetOrAdd(mAddress.Station, a =>
|
|
||||||
|
if (ModbusServer03ByteBlocks.ContainsKey(mAddress.Station))
|
||||||
|
return;
|
||||||
|
else
|
||||||
|
ModbusServer03ByteBlocks.GetOrAdd(mAddress.Station, a =>
|
||||||
{
|
{
|
||||||
var bytes = new ByteBlock(256,
|
var bytes = new ByteBlock(256,
|
||||||
(c) =>
|
(c) =>
|
||||||
@@ -244,35 +284,11 @@ public class ModbusSlave : DeviceBase, IModbusAddress
|
|||||||
bytes.Position = 0;
|
bytes.Position = 0;
|
||||||
return bytes;
|
return bytes;
|
||||||
});
|
});
|
||||||
ModbusServer03ByteBlocks.GetOrAdd(mAddress.Station, a =>
|
|
||||||
{
|
if (ModbusServer04ByteBlocks.ContainsKey(mAddress.Station))
|
||||||
var bytes = new ByteBlock(256,
|
return;
|
||||||
(c) =>
|
else
|
||||||
{
|
ModbusServer04ByteBlocks.GetOrAdd(mAddress.Station, a =>
|
||||||
var data = ArrayPool<byte>.Shared.Rent(c);
|
|
||||||
for (int i = 0; i < data.Length; i++)
|
|
||||||
{
|
|
||||||
data[i] = 0;
|
|
||||||
}
|
|
||||||
return data;
|
|
||||||
},
|
|
||||||
(m) =>
|
|
||||||
{
|
|
||||||
if (MemoryMarshal.TryGetArray((ReadOnlyMemory<byte>)m, out var result))
|
|
||||||
{
|
|
||||||
ArrayPool<byte>.Shared.Return(result.Array);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
);
|
|
||||||
bytes.SetLength(256);
|
|
||||||
for (int i = 0; i < bytes.Length; i++)
|
|
||||||
{
|
|
||||||
bytes.WriteByte(0);
|
|
||||||
}
|
|
||||||
bytes.Position = 0;
|
|
||||||
return bytes;
|
|
||||||
});
|
|
||||||
ModbusServer04ByteBlocks.GetOrAdd(mAddress.Station, a =>
|
|
||||||
{
|
{
|
||||||
var bytes = new ByteBlock(256,
|
var bytes = new ByteBlock(256,
|
||||||
(c) =>
|
(c) =>
|
||||||
@@ -337,10 +353,11 @@ public class ModbusSlave : DeviceBase, IModbusAddress
|
|||||||
}
|
}
|
||||||
Init(mAddress);
|
Init(mAddress);
|
||||||
}
|
}
|
||||||
var ModbusServer01ByteBlock = ModbusServer01ByteBlocks[mAddress.Station];
|
|
||||||
var ModbusServer02ByteBlock = ModbusServer02ByteBlocks[mAddress.Station];
|
ModbusServer01ByteBlocks.TryGetValue(mAddress.Station, out var ModbusServer01ByteBlock);
|
||||||
var ModbusServer03ByteBlock = ModbusServer03ByteBlocks[mAddress.Station];
|
ModbusServer02ByteBlocks.TryGetValue(mAddress.Station, out var ModbusServer02ByteBlock);
|
||||||
var ModbusServer04ByteBlock = ModbusServer04ByteBlocks[mAddress.Station];
|
ModbusServer03ByteBlocks.TryGetValue(mAddress.Station, out var ModbusServer03ByteBlock);
|
||||||
|
ModbusServer04ByteBlocks.TryGetValue(mAddress.Station, out var ModbusServer04ByteBlock);
|
||||||
if (read)
|
if (read)
|
||||||
{
|
{
|
||||||
using (new ReadLock(_lockSlim))
|
using (new ReadLock(_lockSlim))
|
||||||
@@ -460,33 +477,31 @@ public class ModbusSlave : DeviceBase, IModbusAddress
|
|||||||
return mAddress;
|
return mAddress;
|
||||||
}
|
}
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public override async ValueTask<OperResult> WriteAsync(string address, ReadOnlyMemory<byte> value, DataTypeEnum dataType, CancellationToken cancellationToken = default)
|
public override ValueTask<OperResult> WriteAsync(string address, ReadOnlyMemory<byte> value, DataTypeEnum dataType, CancellationToken cancellationToken = default)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
await EasyValueTask.CompletedTask.ConfigureAwait(false);
|
|
||||||
var mAddress = GetModbusAddress(address, Station);
|
var mAddress = GetModbusAddress(address, Station);
|
||||||
mAddress.SlaveWriteDatas = new(value);
|
mAddress.SlaveWriteDatas = new(value);
|
||||||
return ModbusRequest(mAddress, false, cancellationToken);
|
return EasyValueTask.FromResult<OperResult>(ModbusRequest(mAddress, false, cancellationToken));
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
return new OperResult(ex);
|
return EasyValueTask.FromResult(new OperResult(ex));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public override async ValueTask<OperResult> WriteAsync(string address, ReadOnlyMemory<bool> value, CancellationToken cancellationToken = default)
|
public override ValueTask<OperResult> WriteAsync(string address, ReadOnlyMemory<bool> value, CancellationToken cancellationToken = default)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
await EasyValueTask.CompletedTask.ConfigureAwait(false);
|
|
||||||
var mAddress = GetModbusAddress(address, Station);
|
var mAddress = GetModbusAddress(address, Station);
|
||||||
if (mAddress.IsBitFunction)
|
if (mAddress.IsBitFunction)
|
||||||
{
|
{
|
||||||
mAddress.SlaveWriteDatas = new(value.Span.BoolToByte());
|
mAddress.SlaveWriteDatas = new(value.Span.BoolToByte());
|
||||||
ModbusRequest(mAddress, false, cancellationToken);
|
ModbusRequest(mAddress, false, cancellationToken);
|
||||||
return OperResult.Success;
|
return EasyValueTask.FromResult(OperResult.Success);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -494,7 +509,7 @@ public class ModbusSlave : DeviceBase, IModbusAddress
|
|||||||
{
|
{
|
||||||
mAddress.Length = 2;
|
mAddress.Length = 2;
|
||||||
var readData = ModbusRequest(mAddress, true, cancellationToken);
|
var readData = ModbusRequest(mAddress, true, cancellationToken);
|
||||||
if (!readData.IsSuccess) return readData;
|
if (!readData.IsSuccess) return EasyValueTask.FromResult<OperResult>(readData);
|
||||||
var writeData = TouchSocketBitConverter.BigEndian.To<ushort>(readData.Content.Span);
|
var writeData = TouchSocketBitConverter.BigEndian.To<ushort>(readData.Content.Span);
|
||||||
var span = value.Span;
|
var span = value.Span;
|
||||||
for (int i = 0; i < value.Length; i++)
|
for (int i = 0; i < value.Length; i++)
|
||||||
@@ -503,41 +518,47 @@ public class ModbusSlave : DeviceBase, IModbusAddress
|
|||||||
}
|
}
|
||||||
mAddress.SlaveWriteDatas = new(ThingsGatewayBitConverter.GetBytes(writeData));
|
mAddress.SlaveWriteDatas = new(ThingsGatewayBitConverter.GetBytes(writeData));
|
||||||
ModbusRequest(mAddress, false, cancellationToken);
|
ModbusRequest(mAddress, false, cancellationToken);
|
||||||
return OperResult.Success;
|
return EasyValueTask.FromResult<OperResult>(OperResult.Success);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
return new OperResult(string.Format(AppResource.ValueOverlimit, nameof(mAddress.BitIndex), 16));
|
return EasyValueTask.FromResult<OperResult>(new OperResult(string.Format(AppResource.ValueOverlimit, nameof(mAddress.BitIndex), 16)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
return new OperResult(ex);
|
return EasyValueTask.FromResult<OperResult>(new OperResult(ex));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
protected override Task ChannelReceived(IClientChannel client, ReceivedDataEventArgs e, bool last)
|
protected override ValueTask ChannelReceived(IClientChannel client, ReceivedDataEventArgs e, bool last)
|
||||||
{
|
{
|
||||||
return HandleChannelReceivedAsync(client, e, last);
|
return HandleChannelReceivedAsync(client, e, last);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task HandleChannelReceivedAsync(IClientChannel client, ReceivedDataEventArgs e, bool last)
|
private ValueTask HandleChannelReceivedAsync(IClientChannel client, ReceivedDataEventArgs e, bool last)
|
||||||
{
|
{
|
||||||
if (!TryParseRequest(e.RequestInfo, out var modbusRequest, out var sequences, out var modbusRtu))
|
return HandleChannelReceivedAsync(this, client, e);
|
||||||
|
|
||||||
|
static async PooledValueTask HandleChannelReceivedAsync(ModbusSlave @this, IClientChannel client, ReceivedDataEventArgs e)
|
||||||
|
{
|
||||||
|
if (!TryParseRequest(e.RequestInfo, out var modbusRequest, out var sequences, out var modbusRtu))
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (!@this.MulStation && modbusRequest.Station != @this.Station)
|
||||||
|
return;
|
||||||
|
|
||||||
|
var function = NormalizeFunctionCode(modbusRequest.FunctionCode);
|
||||||
|
|
||||||
|
if (function <= 4)
|
||||||
|
await @this.HandleReadRequestAsync(client, e, modbusRequest, sequences, modbusRtu).ConfigureAwait(false);
|
||||||
|
else
|
||||||
|
await @this.HandleWriteRequestAsync(client, e, modbusRequest, sequences, modbusRtu, function).ConfigureAwait(false);
|
||||||
return;
|
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)
|
private static bool TryParseRequest(IRequestInfo requestInfo, out ModbusRequest modbusRequest, out ReadOnlySequence<byte> sequences, out bool modbusRtu)
|
||||||
{
|
{
|
||||||
modbusRequest = default;
|
modbusRequest = default;
|
||||||
sequences = default;
|
sequences = default;
|
||||||
@@ -565,7 +586,7 @@ public class ModbusSlave : DeviceBase, IModbusAddress
|
|||||||
private static byte NormalizeFunctionCode(byte funcCode)
|
private static byte NormalizeFunctionCode(byte funcCode)
|
||||||
=> funcCode > 0x30 ? (byte)(funcCode - 0x30) : funcCode;
|
=> funcCode > 0x30 ? (byte)(funcCode - 0x30) : funcCode;
|
||||||
|
|
||||||
private async Task HandleReadRequestAsync(
|
private Task HandleReadRequestAsync(
|
||||||
IClientChannel client,
|
IClientChannel client,
|
||||||
ReceivedDataEventArgs e,
|
ReceivedDataEventArgs e,
|
||||||
ModbusRequest modbusRequest,
|
ModbusRequest modbusRequest,
|
||||||
@@ -575,27 +596,31 @@ public class ModbusSlave : DeviceBase, IModbusAddress
|
|||||||
var data = ModbusRequest(modbusRequest, true);
|
var data = ModbusRequest(modbusRequest, true);
|
||||||
if (!data.IsSuccess)
|
if (!data.IsSuccess)
|
||||||
{
|
{
|
||||||
await WriteError(modbusRtu, client, sequences, e).ConfigureAwait(false);
|
return WriteError(modbusRtu, client, sequences, e);
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ValueByteBlock byteBlock = new(1024);
|
return Write(this, client, e, modbusRequest, sequences, modbusRtu, data);
|
||||||
try
|
|
||||||
|
static async PooledTask Write(ModbusSlave @this, IClientChannel client, ReceivedDataEventArgs e, ModbusRequest modbusRequest, ReadOnlySequence<byte> sequences, bool modbusRtu, OperResult<ReadOnlyMemory<byte>> data)
|
||||||
{
|
{
|
||||||
WriteReadResponse(modbusRequest, sequences, data.Content, ref byteBlock, modbusRtu);
|
ValueByteBlock byteBlock = new(1024);
|
||||||
await ReturnData(client, byteBlock.Memory, e).ConfigureAwait(false);
|
try
|
||||||
}
|
{
|
||||||
catch
|
WriteReadResponse(modbusRequest, sequences, data.Content, ref byteBlock, modbusRtu);
|
||||||
{
|
await @this.ReturnData(client, byteBlock.Memory, e).ConfigureAwait(false);
|
||||||
await WriteError(modbusRtu, client, sequences, e).ConfigureAwait(false);
|
}
|
||||||
}
|
catch
|
||||||
finally
|
{
|
||||||
{
|
await @this.WriteError(modbusRtu, client, sequences, e).ConfigureAwait(false);
|
||||||
byteBlock.SafeDispose();
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
byteBlock.SafeDispose();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task HandleWriteRequestAsync(
|
private Task HandleWriteRequestAsync(
|
||||||
IClientChannel client,
|
IClientChannel client,
|
||||||
ReceivedDataEventArgs e,
|
ReceivedDataEventArgs e,
|
||||||
ModbusRequest modbusRequest,
|
ModbusRequest modbusRequest,
|
||||||
@@ -603,50 +628,62 @@ public class ModbusSlave : DeviceBase, IModbusAddress
|
|||||||
bool modbusRtu,
|
bool modbusRtu,
|
||||||
byte f)
|
byte f)
|
||||||
{
|
{
|
||||||
var modbusAddress = new ModbusAddress(modbusRequest);
|
return HandleWriteRequestAsync(this, client, e, modbusRequest, sequences, modbusRtu, f);
|
||||||
bool isSuccess;
|
|
||||||
|
|
||||||
switch (f)
|
|
||||||
|
static async PooledTask HandleWriteRequestAsync(ModbusSlave @this, IClientChannel client, ReceivedDataEventArgs e, ModbusRequest modbusRequest, ReadOnlySequence<byte> sequences, bool modbusRtu, byte f)
|
||||||
{
|
{
|
||||||
case 5:
|
var modbusAddress = new ModbusAddress(modbusRequest);
|
||||||
case 15:
|
bool isSuccess;
|
||||||
modbusAddress.WriteFunctionCode = modbusRequest.FunctionCode;
|
|
||||||
modbusAddress.FunctionCode = 1;
|
|
||||||
isSuccess = await HandleWriteCoreAsync(modbusAddress, client, modbusRequest).ConfigureAwait(false);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 6:
|
switch (f)
|
||||||
case 16:
|
{
|
||||||
modbusAddress.WriteFunctionCode = modbusRequest.FunctionCode;
|
case 5:
|
||||||
modbusAddress.FunctionCode = 3;
|
case 15:
|
||||||
isSuccess = await HandleWriteCoreAsync(modbusAddress, client, modbusRequest).ConfigureAwait(false);
|
modbusAddress.WriteFunctionCode = modbusRequest.FunctionCode;
|
||||||
break;
|
modbusAddress.FunctionCode = 1;
|
||||||
|
isSuccess = await @this.HandleWriteCoreAsync(modbusAddress, client, modbusRequest).ConfigureAwait(false);
|
||||||
|
break;
|
||||||
|
|
||||||
default:
|
case 6:
|
||||||
return;
|
case 16:
|
||||||
|
modbusAddress.WriteFunctionCode = modbusRequest.FunctionCode;
|
||||||
|
modbusAddress.FunctionCode = 3;
|
||||||
|
isSuccess = await @this.HandleWriteCoreAsync(modbusAddress, client, modbusRequest).ConfigureAwait(false);
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isSuccess)
|
||||||
|
await @this.WriteSuccess(modbusRtu, client, sequences, e).ConfigureAwait(false);
|
||||||
|
else
|
||||||
|
await @this.WriteError(modbusRtu, client, sequences, e).ConfigureAwait(false);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isSuccess)
|
|
||||||
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)
|
private Task<bool> HandleWriteCoreAsync(ModbusAddress address, IClientChannel client, ModbusRequest modbusRequest)
|
||||||
{
|
{
|
||||||
if (WriteData != null)
|
return HandleWriteCoreAsync(this, address, client, modbusRequest);
|
||||||
{
|
|
||||||
var result = await WriteData(address, ThingsGatewayBitConverter, client).ConfigureAwait(false);
|
|
||||||
if (!result.IsSuccess) return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (IsWriteMemory)
|
static async PooledTask<bool> HandleWriteCoreAsync(ModbusSlave @this, ModbusAddress address, IClientChannel client, ModbusRequest modbusRequest)
|
||||||
{
|
{
|
||||||
var memResult = ModbusRequest(modbusRequest, false);
|
if (@this.WriteData != null)
|
||||||
return memResult.IsSuccess;
|
{
|
||||||
}
|
var result = await @this.WriteData(address, @this.ThingsGatewayBitConverter, client).ConfigureAwait(false);
|
||||||
|
if (!result.IsSuccess) return false;
|
||||||
|
}
|
||||||
|
|
||||||
return true;
|
if (@this.IsWriteMemory)
|
||||||
|
{
|
||||||
|
var memResult = @this.ModbusRequest(modbusRequest, false);
|
||||||
|
return memResult.IsSuccess;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void WriteReadResponse(
|
private static void WriteReadResponse(
|
||||||
@@ -681,63 +718,78 @@ public class ModbusSlave : DeviceBase, IModbusAddress
|
|||||||
ByteBlockExtension.WriteBackValue(ref byteBlock, (byte)(byteBlock.Length - 6), EndianType.Big, 5);
|
ByteBlockExtension.WriteBackValue(ref byteBlock, (byte)(byteBlock.Length - 6), EndianType.Big, 5);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task ReturnData(IClientChannel client, ReadOnlyMemory<byte> sendData, ReceivedDataEventArgs e)
|
private Task ReturnData(IClientChannel client, ReadOnlyMemory<byte> sendData, ReceivedDataEventArgs e)
|
||||||
{
|
{
|
||||||
if (SendDelayTime > 0)
|
return ReturnData(SendDelayTime, client, sendData, e);
|
||||||
await Task.Delay(SendDelayTime).ConfigureAwait(false);
|
|
||||||
if (client is IUdpClientSender udpClientSender)
|
|
||||||
await udpClientSender.SendAsync(((UdpReceivedDataEventArgs)e).EndPoint, sendData).ConfigureAwait(false);
|
|
||||||
else
|
|
||||||
await client.SendAsync(sendData, client.ClosedToken).ConfigureAwait(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task WriteError(bool modbusRtu, IClientChannel client, ReadOnlySequence<byte> bytes, ReceivedDataEventArgs e)
|
static async PooledTask ReturnData(int deley, IClientChannel client, ReadOnlyMemory<byte> sendData, ReceivedDataEventArgs e)
|
||||||
{
|
|
||||||
ValueByteBlock byteBlock = new(20);
|
|
||||||
try
|
|
||||||
{
|
{
|
||||||
if (modbusRtu)
|
if (deley > 0)
|
||||||
{
|
await Task.Delay(deley).ConfigureAwait(false);
|
||||||
ByteBlockExtension.Write(ref byteBlock, bytes.Slice(0, 2));
|
if (client is IUdpClientSender udpClientSender)
|
||||||
WriterExtension.WriteValue(ref byteBlock, (byte)1);
|
await udpClientSender.SendAsync(((UdpReceivedDataEventArgs)e).EndPoint, sendData).ConfigureAwait(false);
|
||||||
byteBlock.Write(CRC16Utils.Crc16Only(byteBlock.Span));
|
|
||||||
ByteBlockExtension.WriteBackValue(ref byteBlock, (byte)(byteBlock.Span[1] + 128), EndianType.Big, 1);
|
|
||||||
}
|
|
||||||
else
|
else
|
||||||
{
|
await client.SendAsync(sendData, client.ClosedToken).ConfigureAwait(false);
|
||||||
ByteBlockExtension.Write(ref byteBlock, bytes.Slice(0, 8));
|
|
||||||
WriterExtension.WriteValue(ref byteBlock, (byte)1);
|
|
||||||
ByteBlockExtension.WriteBackValue(ref byteBlock, (byte)(byteBlock.Length - 6), EndianType.Big, 5);
|
|
||||||
ByteBlockExtension.WriteBackValue(ref byteBlock, (byte)(byteBlock.Span[7] + 128), EndianType.Big, 7);
|
|
||||||
}
|
|
||||||
await ReturnData(client, byteBlock.Memory, e).ConfigureAwait(false);
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
byteBlock.SafeDispose();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task WriteSuccess(bool modbusRtu, IClientChannel client, ReadOnlySequence<byte> bytes, ReceivedDataEventArgs e)
|
private Task WriteError(bool modbusRtu, IClientChannel client, ReadOnlySequence<byte> bytes, ReceivedDataEventArgs e)
|
||||||
{
|
{
|
||||||
ValueByteBlock byteBlock = new(20);
|
return WriteError(this, modbusRtu, client, bytes, e);
|
||||||
try
|
|
||||||
|
static async PooledTask WriteError(ModbusSlave @this, bool modbusRtu, IClientChannel client, ReadOnlySequence<byte> bytes, ReceivedDataEventArgs e)
|
||||||
{
|
{
|
||||||
if (modbusRtu)
|
ValueByteBlock byteBlock = new(20);
|
||||||
|
try
|
||||||
{
|
{
|
||||||
ByteBlockExtension.Write(ref byteBlock, bytes.Slice(0, 6));
|
if (modbusRtu)
|
||||||
byteBlock.Write(CRC16Utils.Crc16Only(byteBlock.Span));
|
{
|
||||||
|
ByteBlockExtension.Write(ref byteBlock, bytes.Slice(0, 2));
|
||||||
|
WriterExtension.WriteValue(ref byteBlock, (byte)1);
|
||||||
|
byteBlock.Write(CRC16Utils.Crc16Only(byteBlock.Span));
|
||||||
|
ByteBlockExtension.WriteBackValue(ref byteBlock, (byte)(byteBlock.Span[1] + 128), EndianType.Big, 1);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
ByteBlockExtension.Write(ref byteBlock, bytes.Slice(0, 8));
|
||||||
|
WriterExtension.WriteValue(ref byteBlock, (byte)1);
|
||||||
|
ByteBlockExtension.WriteBackValue(ref byteBlock, (byte)(byteBlock.Length - 6), EndianType.Big, 5);
|
||||||
|
ByteBlockExtension.WriteBackValue(ref byteBlock, (byte)(byteBlock.Span[7] + 128), EndianType.Big, 7);
|
||||||
|
}
|
||||||
|
await @this.ReturnData(client, byteBlock.Memory, e).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
else
|
finally
|
||||||
{
|
{
|
||||||
ByteBlockExtension.Write(ref byteBlock, bytes.Slice(0, 12));
|
byteBlock.SafeDispose();
|
||||||
ByteBlockExtension.WriteBackValue(ref byteBlock, (byte)(byteBlock.Length - 6), EndianType.Big, 5);
|
|
||||||
}
|
}
|
||||||
await ReturnData(client, byteBlock.Memory, e).ConfigureAwait(false);
|
|
||||||
}
|
}
|
||||||
finally
|
}
|
||||||
|
|
||||||
|
private Task WriteSuccess(bool modbusRtu, IClientChannel client, ReadOnlySequence<byte> bytes, ReceivedDataEventArgs e)
|
||||||
|
{
|
||||||
|
return WriteSuccess(this, modbusRtu, client, bytes, e);
|
||||||
|
|
||||||
|
static async PooledTask WriteSuccess(ModbusSlave @this, bool modbusRtu, IClientChannel client, ReadOnlySequence<byte> bytes, ReceivedDataEventArgs e)
|
||||||
{
|
{
|
||||||
byteBlock.SafeDispose();
|
ValueByteBlock byteBlock = new(20);
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (modbusRtu)
|
||||||
|
{
|
||||||
|
ByteBlockExtension.Write(ref byteBlock, bytes.Slice(0, 6));
|
||||||
|
byteBlock.Write(CRC16Utils.Crc16Only(byteBlock.Span));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
ByteBlockExtension.Write(ref byteBlock, bytes.Slice(0, 12));
|
||||||
|
ByteBlockExtension.WriteBackValue(ref byteBlock, (byte)(byteBlock.Length - 6), EndianType.Big, 5);
|
||||||
|
}
|
||||||
|
await @this.ReturnData(client, byteBlock.Memory, e).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
byteBlock.SafeDispose();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -395,18 +395,17 @@ public class OpcUaMaster : IAsyncDisposable
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// 连接到服务器
|
/// 连接到服务器
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public async Task ConnectAsync(CancellationToken cancellationToken)
|
public Task ConnectAsync(CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
await ConnectAsync(OpcUaProperty.OpcUrl, cancellationToken).ConfigureAwait(false);
|
return ConnectAsync(OpcUaProperty.OpcUrl, cancellationToken);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 断开连接。
|
/// 断开连接。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public async Task DisconnectAsync()
|
public Task DisconnectAsync()
|
||||||
{
|
{
|
||||||
await PrivateDisconnectAsync().ConfigureAwait(false);
|
return PrivateDisconnectAsync();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async ValueTask DisposeAsync()
|
public async ValueTask DisposeAsync()
|
||||||
|
|||||||
@@ -8,6 +8,8 @@
|
|||||||
// QQ群:605534569
|
// QQ群:605534569
|
||||||
//------------------------------------------------------------------------------
|
//------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
using PooledAwait;
|
||||||
|
|
||||||
using ThingsGateway.Foundation.Extension.String;
|
using ThingsGateway.Foundation.Extension.String;
|
||||||
using ThingsGateway.NewLife;
|
using ThingsGateway.NewLife;
|
||||||
using ThingsGateway.NewLife.Extension;
|
using ThingsGateway.NewLife.Extension;
|
||||||
@@ -144,71 +146,76 @@ public partial class SiemensS7Master : DeviceBase
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// 此方法并不会智能分组以最大化效率,减少传输次数,因为返回值是byte[],所以一切都按地址数组的顺序执行,最后合并数组
|
/// 此方法并不会智能分组以最大化效率,减少传输次数,因为返回值是byte[],所以一切都按地址数组的顺序执行,最后合并数组
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public async ValueTask<OperResult<ReadOnlyMemory<byte>>> S7ReadAsync(
|
public ValueTask<OperResult<ReadOnlyMemory<byte>>> S7ReadAsync(
|
||||||
SiemensS7Address[] addresses,
|
SiemensS7Address[] addresses,
|
||||||
CancellationToken cancellationToken = default)
|
CancellationToken cancellationToken = default)
|
||||||
{
|
{
|
||||||
var byteBuffer = new ValueByteBlock(512);
|
return S7ReadAsync(this, addresses, cancellationToken);
|
||||||
|
|
||||||
try
|
static async PooledValueTask<OperResult<ReadOnlyMemory<byte>>> S7ReadAsync(SiemensS7Master @this, SiemensS7Address[] addresses, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
foreach (var address in addresses)
|
var byteBuffer = new ValueByteBlock(512);
|
||||||
|
|
||||||
|
try
|
||||||
{
|
{
|
||||||
int readCount = 0;
|
foreach (var address in addresses)
|
||||||
int totalLength = address.Length == 0 ? 1 : address.Length;
|
|
||||||
int originalStart = address.AddressStart;
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
{
|
||||||
while (readCount < totalLength)
|
int readCount = 0;
|
||||||
|
int totalLength = address.Length == 0 ? 1 : address.Length;
|
||||||
|
int originalStart = address.AddressStart;
|
||||||
|
|
||||||
|
try
|
||||||
{
|
{
|
||||||
// 每次读取的 PDU 长度,循环直到读取完整
|
while (readCount < totalLength)
|
||||||
int chunkLength = Math.Min(totalLength - readCount, PduLength);
|
|
||||||
address.Length = chunkLength;
|
|
||||||
|
|
||||||
var result = await SendThenReturnAsync(
|
|
||||||
new S7Send([address], true),
|
|
||||||
cancellationToken: cancellationToken
|
|
||||||
).ConfigureAwait(false);
|
|
||||||
|
|
||||||
if (!result.IsSuccess)
|
|
||||||
return result;
|
|
||||||
|
|
||||||
byteBuffer.Write(result.Content.Span);
|
|
||||||
|
|
||||||
if (readCount + chunkLength >= totalLength)
|
|
||||||
{
|
{
|
||||||
if (addresses.Length == 1)
|
// 每次读取的 PDU 长度,循环直到读取完整
|
||||||
{
|
int chunkLength = Math.Min(totalLength - readCount, @this.PduLength);
|
||||||
|
address.Length = chunkLength;
|
||||||
|
|
||||||
|
var result = await @this.SendThenReturnAsync(
|
||||||
|
new S7Send([address], true),
|
||||||
|
cancellationToken: cancellationToken
|
||||||
|
).ConfigureAwait(false);
|
||||||
|
|
||||||
|
if (!result.IsSuccess)
|
||||||
return result;
|
return result;
|
||||||
|
|
||||||
|
byteBuffer.Write(result.Content.Span);
|
||||||
|
|
||||||
|
if (readCount + chunkLength >= totalLength)
|
||||||
|
{
|
||||||
|
if (addresses.Length == 1)
|
||||||
|
{
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
break;
|
|
||||||
|
readCount += chunkLength;
|
||||||
|
|
||||||
|
// 更新地址起点
|
||||||
|
if (address.DataCode == S7Area.TM || address.DataCode == S7Area.CT)
|
||||||
|
address.AddressStart += chunkLength / 2;
|
||||||
|
else
|
||||||
|
address.AddressStart += chunkLength * 8;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
readCount += chunkLength;
|
finally
|
||||||
|
{
|
||||||
// 更新地址起点
|
address.AddressStart = originalStart;
|
||||||
if (address.DataCode == S7Area.TM || address.DataCode == S7Area.CT)
|
|
||||||
address.AddressStart += chunkLength / 2;
|
|
||||||
else
|
|
||||||
address.AddressStart += chunkLength * 8;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
finally
|
|
||||||
{
|
|
||||||
address.AddressStart = originalStart;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return new OperResult<ReadOnlyMemory<byte>> { Content = byteBuffer.ToArray() };
|
return new OperResult<ReadOnlyMemory<byte>> { Content = byteBuffer.ToArray() };
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
return new OperResult<ReadOnlyMemory<byte>>(ex);
|
return new OperResult<ReadOnlyMemory<byte>>(ex);
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
byteBuffer.SafeDispose();
|
byteBuffer.SafeDispose();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -216,79 +223,65 @@ public partial class SiemensS7Master : DeviceBase
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// 此方法并不会智能分组以最大化效率,减少传输次数,因为返回值是byte[],所以一切都按地址数组的顺序执行,最后合并数组
|
/// 此方法并不会智能分组以最大化效率,减少传输次数,因为返回值是byte[],所以一切都按地址数组的顺序执行,最后合并数组
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public async ValueTask<Dictionary<SiemensS7Address, OperResult>> S7WriteAsync(
|
public ValueTask<Dictionary<SiemensS7Address, OperResult>> S7WriteAsync(
|
||||||
SiemensS7Address[] addresses,
|
SiemensS7Address[] addresses,
|
||||||
CancellationToken cancellationToken = default)
|
CancellationToken cancellationToken = default)
|
||||||
{
|
{
|
||||||
var dictOperResult = new Dictionary<SiemensS7Address, OperResult>();
|
return S7WriteAsync(this, addresses, cancellationToken);
|
||||||
|
|
||||||
void SetFailOperResult(OperResult operResult)
|
static async PooledValueTask<Dictionary<SiemensS7Address, OperResult>> S7WriteAsync(SiemensS7Master @this, SiemensS7Address[] addresses, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
foreach (var address in addresses)
|
var dictOperResult = new Dictionary<SiemensS7Address, OperResult>();
|
||||||
{
|
|
||||||
dictOperResult.TryAdd(address, operResult);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var firstAddress = addresses[0];
|
void SetFailOperResult(OperResult operResult)
|
||||||
|
|
||||||
// 单位写入(位写入)
|
|
||||||
if (addresses.Length <= 1 && firstAddress.IsBit)
|
|
||||||
{
|
|
||||||
var byteBuffer = new ValueByteBlock(512);
|
|
||||||
try
|
|
||||||
{
|
{
|
||||||
var writeResult = await SendThenReturnAsync(
|
foreach (var address in addresses)
|
||||||
new S7Send([firstAddress], false),
|
|
||||||
cancellationToken: cancellationToken
|
|
||||||
).ConfigureAwait(false);
|
|
||||||
|
|
||||||
dictOperResult.TryAdd(firstAddress, writeResult);
|
|
||||||
return dictOperResult;
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
SetFailOperResult(new OperResult(ex));
|
|
||||||
return dictOperResult;
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
byteBuffer.SafeDispose();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// 多写入
|
|
||||||
var addressChunks = new List<List<SiemensS7Address>>();
|
|
||||||
ushort dataLength = 0;
|
|
||||||
ushort itemCount = 1;
|
|
||||||
var currentChunk = new List<SiemensS7Address>();
|
|
||||||
|
|
||||||
for (int i = 0; i < addresses.Length; i++)
|
|
||||||
{
|
|
||||||
var address = addresses[i];
|
|
||||||
dataLength += (ushort)(address.Data.Length + 4);
|
|
||||||
ushort telegramLength = (ushort)(itemCount * 12 + 19 + dataLength);
|
|
||||||
|
|
||||||
if (telegramLength < PduLength)
|
|
||||||
{
|
{
|
||||||
currentChunk.Add(address);
|
dictOperResult.TryAdd(address, operResult);
|
||||||
itemCount++;
|
|
||||||
|
|
||||||
if (i == addresses.Length - 1)
|
|
||||||
addressChunks.Add(currentChunk);
|
|
||||||
}
|
}
|
||||||
else
|
}
|
||||||
|
|
||||||
|
var firstAddress = addresses[0];
|
||||||
|
|
||||||
|
// 单位写入(位写入)
|
||||||
|
if (addresses.Length <= 1 && firstAddress.IsBit)
|
||||||
|
{
|
||||||
|
var byteBuffer = new ValueByteBlock(512);
|
||||||
|
try
|
||||||
{
|
{
|
||||||
addressChunks.Add(currentChunk);
|
var writeResult = await @this.SendThenReturnAsync(
|
||||||
currentChunk = new List<SiemensS7Address>();
|
new S7Send([firstAddress], false),
|
||||||
dataLength = 0;
|
cancellationToken: cancellationToken
|
||||||
itemCount = 1;
|
).ConfigureAwait(false);
|
||||||
|
|
||||||
|
dictOperResult.TryAdd(firstAddress, writeResult);
|
||||||
|
return dictOperResult;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
SetFailOperResult(new OperResult(ex));
|
||||||
|
return dictOperResult;
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
byteBuffer.SafeDispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// 多写入
|
||||||
|
var addressChunks = new List<List<SiemensS7Address>>();
|
||||||
|
ushort dataLength = 0;
|
||||||
|
ushort itemCount = 1;
|
||||||
|
var currentChunk = new List<SiemensS7Address>();
|
||||||
|
|
||||||
|
for (int i = 0; i < addresses.Length; i++)
|
||||||
|
{
|
||||||
|
var address = addresses[i];
|
||||||
dataLength += (ushort)(address.Data.Length + 4);
|
dataLength += (ushort)(address.Data.Length + 4);
|
||||||
telegramLength = (ushort)(itemCount * 12 + 19 + dataLength);
|
ushort telegramLength = (ushort)(itemCount * 12 + 19 + dataLength);
|
||||||
|
|
||||||
if (telegramLength < PduLength)
|
if (telegramLength < @this.PduLength)
|
||||||
{
|
{
|
||||||
currentChunk.Add(address);
|
currentChunk.Add(address);
|
||||||
itemCount++;
|
itemCount++;
|
||||||
@@ -298,34 +291,53 @@ public partial class SiemensS7Master : DeviceBase
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
SetFailOperResult(new OperResult("Write length exceeds limit"));
|
addressChunks.Add(currentChunk);
|
||||||
|
currentChunk = new List<SiemensS7Address>();
|
||||||
|
dataLength = 0;
|
||||||
|
itemCount = 1;
|
||||||
|
|
||||||
|
dataLength += (ushort)(address.Data.Length + 4);
|
||||||
|
telegramLength = (ushort)(itemCount * 12 + 19 + dataLength);
|
||||||
|
|
||||||
|
if (telegramLength < @this.PduLength)
|
||||||
|
{
|
||||||
|
currentChunk.Add(address);
|
||||||
|
itemCount++;
|
||||||
|
|
||||||
|
if (i == addresses.Length - 1)
|
||||||
|
addressChunks.Add(currentChunk);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
SetFailOperResult(new OperResult("Write length exceeds limit"));
|
||||||
|
return dictOperResult;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var chunk in addressChunks)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var result = await @this.SendThenReturnAsync(
|
||||||
|
new S7Send(chunk.ToArray(), false),
|
||||||
|
cancellationToken: cancellationToken
|
||||||
|
).ConfigureAwait(false);
|
||||||
|
|
||||||
|
foreach (var addr in chunk)
|
||||||
|
{
|
||||||
|
dictOperResult.TryAdd(addr, result);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
SetFailOperResult(new OperResult(ex));
|
||||||
return dictOperResult;
|
return dictOperResult;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return dictOperResult;
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach (var chunk in addressChunks)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var result = await SendThenReturnAsync(
|
|
||||||
new S7Send(chunk.ToArray(), false),
|
|
||||||
cancellationToken: cancellationToken
|
|
||||||
).ConfigureAwait(false);
|
|
||||||
|
|
||||||
foreach (var addr in chunk)
|
|
||||||
{
|
|
||||||
dictOperResult.TryAdd(addr, result);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
SetFailOperResult(new OperResult(ex));
|
|
||||||
return dictOperResult;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return dictOperResult;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -8,6 +8,8 @@
|
|||||||
// QQ群:605534569
|
// QQ群:605534569
|
||||||
//------------------------------------------------------------------------------
|
//------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
using PooledAwait;
|
||||||
|
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
|
|
||||||
using ThingsGateway.Extension.Generic;
|
using ThingsGateway.Extension.Generic;
|
||||||
@@ -81,91 +83,101 @@ public partial class QuestDBProducer : BusinessBaseWithCacheIntervalVariable
|
|||||||
AddQueueVarModel(new CacheDBItem<VariableBasicData>(variable));
|
AddQueueVarModel(new CacheDBItem<VariableBasicData>(variable));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
private async ValueTask<OperResult> UpdateVarModel(IEnumerable<VariableBasicData> item, CancellationToken cancellationToken)
|
private ValueTask<OperResult> UpdateVarModel(IEnumerable<VariableBasicData> item, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
var result = await InserableAsync(item.WhereIf(_driverPropertys.OnlineFilter, a => a.IsOnline == true).ToList(), cancellationToken).ConfigureAwait(false);
|
return UpdateVarModel(this, item, cancellationToken);
|
||||||
if (success != result.IsSuccess)
|
|
||||||
{
|
|
||||||
if (!result.IsSuccess)
|
|
||||||
LogMessage?.LogWarning(result.ToString());
|
|
||||||
success = result.IsSuccess;
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
static async PooledValueTask<OperResult> UpdateVarModel(QuestDBProducer @this, IEnumerable<VariableBasicData> item, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
var result = await @this.InserableAsync(item.WhereIf(@this._driverPropertys.OnlineFilter, a => a.IsOnline == true).ToList(), cancellationToken).ConfigureAwait(false);
|
||||||
|
if (@this.success != result.IsSuccess)
|
||||||
|
{
|
||||||
|
if (!result.IsSuccess)
|
||||||
|
@this.LogMessage?.LogWarning(result.ToString());
|
||||||
|
@this.success = result.IsSuccess;
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#region 方法
|
#region 方法
|
||||||
|
|
||||||
private async ValueTask<OperResult> InserableAsync(List<VariableBasicData> dbInserts, CancellationToken cancellationToken)
|
private ValueTask<OperResult> InserableAsync(List<VariableBasicData> dbInserts, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
try
|
return InserableAsync(this, dbInserts, cancellationToken);
|
||||||
|
|
||||||
|
static async PooledValueTask<OperResult> InserableAsync(QuestDBProducer @this, List<VariableBasicData> dbInserts, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
_db.Ado.CancellationToken = cancellationToken;
|
try
|
||||||
if (!_driverPropertys.BigTextScriptHistoryTable.IsNullOrEmpty())
|
|
||||||
{
|
{
|
||||||
var getDeviceModel = CSharpScriptEngineExtension.Do<DynamicSQLBase>(_driverPropertys.BigTextScriptHistoryTable);
|
@this._db.Ado.CancellationToken = cancellationToken;
|
||||||
getDeviceModel.Logger = LogMessage;
|
if (!@this._driverPropertys.BigTextScriptHistoryTable.IsNullOrEmpty())
|
||||||
|
|
||||||
await getDeviceModel.DBInsertable(_db, dbInserts, cancellationToken).ConfigureAwait(false);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
var stringData = dbInserts.Where(a => (!a.IsNumber && a.Value is not bool));
|
|
||||||
var numberData = dbInserts.Where(a => (a.IsNumber || a.Value is bool));
|
|
||||||
|
|
||||||
if (numberData.Any())
|
|
||||||
{
|
{
|
||||||
Stopwatch stopwatch = new();
|
var getDeviceModel = CSharpScriptEngineExtension.Do<DynamicSQLBase>(@this._driverPropertys.BigTextScriptHistoryTable);
|
||||||
stopwatch.Start();
|
getDeviceModel.Logger = @this.LogMessage;
|
||||||
var data = numberData.AdaptListQuestDBNumberHistoryValue();
|
|
||||||
int result = 0;
|
|
||||||
if (_driverPropertys.RestApi)
|
|
||||||
{
|
|
||||||
result = await _db.RestApi(_driverPropertys.HttpPort).BulkCopyAsync(data, _driverPropertys.NumberTableName).ConfigureAwait(false);//不要加分表
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
result = await _db.Insertable(data).AS(_driverPropertys.NumberTableName).ExecuteCommandAsync(cancellationToken).ConfigureAwait(false);//不要加分表
|
|
||||||
}
|
|
||||||
stopwatch.Stop();
|
|
||||||
|
|
||||||
//var result = await db.Insertable(dbInserts).SplitTable().ExecuteCommandAsync().ConfigureAwait(false);
|
await getDeviceModel.DBInsertable(@this._db, dbInserts, cancellationToken).ConfigureAwait(false);
|
||||||
if (result > 0)
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var stringData = dbInserts.Where(a => (!a.IsNumber && a.Value is not bool));
|
||||||
|
var numberData = dbInserts.Where(a => (a.IsNumber || a.Value is bool));
|
||||||
|
|
||||||
|
if (numberData.Any())
|
||||||
{
|
{
|
||||||
LogMessage?.Trace($"TableName:{_driverPropertys.NumberTableName},Count:{result},watchTime: {stopwatch.ElapsedMilliseconds} ms");
|
Stopwatch stopwatch = new();
|
||||||
|
stopwatch.Start();
|
||||||
|
var data = numberData.AdaptListQuestDBNumberHistoryValue();
|
||||||
|
int result = 0;
|
||||||
|
if (@this._driverPropertys.RestApi)
|
||||||
|
{
|
||||||
|
result = await @this._db.RestApi(@this._driverPropertys.HttpPort).BulkCopyAsync(data, @this._driverPropertys.NumberTableName).ConfigureAwait(false);//不要加分表
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
result = await @this._db.Insertable(data).AS(@this._driverPropertys.NumberTableName).ExecuteCommandAsync(cancellationToken).ConfigureAwait(false);//不要加分表
|
||||||
|
}
|
||||||
|
stopwatch.Stop();
|
||||||
|
|
||||||
|
//var result = await db.Insertable(dbInserts).SplitTable().ExecuteCommandAsync().ConfigureAwait(false);
|
||||||
|
if (result > 0)
|
||||||
|
{
|
||||||
|
@this.LogMessage?.Trace($"TableName:{@this._driverPropertys.NumberTableName},Count:{result},watchTime: {stopwatch.ElapsedMilliseconds} ms");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (stringData.Any())
|
||||||
|
{
|
||||||
|
Stopwatch stopwatch = new();
|
||||||
|
stopwatch.Start();
|
||||||
|
var data = stringData.AdaptListQuestDBHistoryValue();
|
||||||
|
int result = 0;
|
||||||
|
if (@this._driverPropertys.RestApi)
|
||||||
|
{
|
||||||
|
result = await @this._db.RestApi(@this._driverPropertys.HttpPort).BulkCopyAsync(data, @this._driverPropertys.StringTableName).ConfigureAwait(false);//不要加分表
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
result = await @this._db.Insertable(data).AS(@this._driverPropertys.StringTableName).ExecuteCommandAsync(cancellationToken).ConfigureAwait(false);//不要加分表
|
||||||
|
}
|
||||||
|
|
||||||
|
stopwatch.Stop();
|
||||||
|
|
||||||
|
//var result = await db.Insertable(dbInserts).SplitTable().ExecuteCommandAsync().ConfigureAwait(false);
|
||||||
|
if (result > 0)
|
||||||
|
{
|
||||||
|
@this.LogMessage?.Trace($"TableName:{@this._driverPropertys.StringTableName},Count:{result},watchTime: {stopwatch.ElapsedMilliseconds} ms");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (stringData.Any())
|
return OperResult.Success;
|
||||||
{
|
}
|
||||||
Stopwatch stopwatch = new();
|
catch (Exception ex)
|
||||||
stopwatch.Start();
|
{
|
||||||
var data = stringData.AdaptListQuestDBHistoryValue();
|
return new OperResult(ex);
|
||||||
int result = 0;
|
|
||||||
if (_driverPropertys.RestApi)
|
|
||||||
{
|
|
||||||
result = await _db.RestApi(_driverPropertys.HttpPort).BulkCopyAsync(data, _driverPropertys.StringTableName).ConfigureAwait(false);//不要加分表
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
result = await _db.Insertable(data).AS(_driverPropertys.StringTableName).ExecuteCommandAsync(cancellationToken).ConfigureAwait(false);//不要加分表
|
|
||||||
}
|
|
||||||
|
|
||||||
stopwatch.Stop();
|
|
||||||
|
|
||||||
//var result = await db.Insertable(dbInserts).SplitTable().ExecuteCommandAsync().ConfigureAwait(false);
|
|
||||||
if (result > 0)
|
|
||||||
{
|
|
||||||
LogMessage?.Trace($"TableName:{_driverPropertys.StringTableName},Count:{result},watchTime: {stopwatch.ElapsedMilliseconds} ms");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return OperResult.Success;
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
return new OperResult(ex);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -10,6 +10,8 @@
|
|||||||
|
|
||||||
using BootstrapBlazor.Components;
|
using BootstrapBlazor.Components;
|
||||||
|
|
||||||
|
using PooledAwait;
|
||||||
|
|
||||||
using ThingsGateway.Common;
|
using ThingsGateway.Common;
|
||||||
using ThingsGateway.DB;
|
using ThingsGateway.DB;
|
||||||
using ThingsGateway.Debug;
|
using ThingsGateway.Debug;
|
||||||
@@ -239,39 +241,44 @@ public partial class SqlDBProducer : BusinessBaseWithCacheIntervalVariable
|
|||||||
await base.ProtectedStartAsync(cancellationToken).ConfigureAwait(false);
|
await base.ProtectedStartAsync(cancellationToken).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override async Task ProtectedExecuteAsync(object? state, CancellationToken cancellationToken)
|
protected override Task ProtectedExecuteAsync(object? state, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
if (_driverPropertys.IsReadDB)
|
return ProtectedExecuteAsync(this, cancellationToken);
|
||||||
|
|
||||||
|
static async PooledTask ProtectedExecuteAsync(SqlDBProducer @this, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
var list = RealTimeVariables.ToListWithDequeue();
|
if (@this._driverPropertys.IsReadDB)
|
||||||
try
|
|
||||||
{
|
{
|
||||||
var varLists = list.Batch(_driverPropertys.SplitSize);
|
var list = @this.RealTimeVariables.ToListWithDequeue();
|
||||||
foreach (var varList in varLists)
|
try
|
||||||
{
|
{
|
||||||
var result = await UpdateAsync(varList, cancellationToken).ConfigureAwait(false);
|
var varLists = list.Batch(@this._driverPropertys.SplitSize);
|
||||||
if (success != result.IsSuccess)
|
foreach (var varList in varLists)
|
||||||
{
|
{
|
||||||
if (!result.IsSuccess)
|
var result = await @this.UpdateAsync(varList, cancellationToken).ConfigureAwait(false);
|
||||||
LogMessage?.LogWarning(result.ToString());
|
if (@this.success != result.IsSuccess)
|
||||||
success = result.IsSuccess;
|
{
|
||||||
|
if (!result.IsSuccess)
|
||||||
|
@this.LogMessage?.LogWarning(result.ToString());
|
||||||
|
@this.success = result.IsSuccess;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
if (@this.success)
|
||||||
|
@this.LogMessage?.LogWarning(ex);
|
||||||
|
@this.success = false;
|
||||||
|
|
||||||
|
list.ForEach(variable => @this.RealTimeVariables.AddOrUpdate(variable.Id, variable, (key, oldValue) => variable));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
|
||||||
|
if (@this._driverPropertys.IsHistoryDB)
|
||||||
{
|
{
|
||||||
if (success)
|
await @this.Update(cancellationToken).ConfigureAwait(false);
|
||||||
LogMessage?.LogWarning(ex);
|
|
||||||
success = false;
|
|
||||||
|
|
||||||
list.ForEach(variable => RealTimeVariables.AddOrUpdate(variable.Id, variable, (key, oldValue) => variable));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_driverPropertys.IsHistoryDB)
|
|
||||||
{
|
|
||||||
await Update(cancellationToken).ConfigureAwait(false);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private ISugarQueryable<SQLNumberHistoryValue> Query(DBHistoryValuePageInput input)
|
private ISugarQueryable<SQLNumberHistoryValue> Query(DBHistoryValuePageInput input)
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user