From 2a8c0cbab1599395466b18ec306bec40a48c8967 Mon Sep 17 00:00:00 2001 From: "2248356998 qq.com" <2248356998@qq.com> Date: Sat, 18 Oct 2025 03:18:45 +0800 Subject: [PATCH] =?UTF-8?q?pref:=20=E5=BC=82=E6=AD=A5=E7=8A=B6=E6=80=81?= =?UTF-8?q?=E6=9C=BA=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ConcurrentDictionary/DictionaryImpl`2.cs | 1 - .../NonBlockingDictionary.cs | 2 - .../Collections/ObjectPool.cs | 10 +- .../Collections/ObjectPoolLock.cs | 262 ++++++++ .../Collections/ValueStopwatch.cs | 48 ++ .../Common/ExpiringDictionary.cs | 2 +- .../Common/WaitLock.cs | 39 +- .../PooledAwait/AwaitableExtensions.cs | 15 + .../PooledAwait/ConfiguredYieldAwaitable.cs | 125 ++++ .../PooledAwait/FireAndForget.cs | 62 ++ .../PooledAwait/IResettable.cs | 13 + .../Internal/BrowsableAttribute.cs | 12 + .../PooledAwait/Internal/Counters.cs | 68 ++ .../PooledAwait/Internal/LazyTaskStateT.cs | 173 +++++ .../ManualResetValueTaskSourceCore.cs | 313 +++++++++ .../PooledAwait/Internal/Nothing.cs | 9 + .../PooledAwait/Internal/PooledStateT.cs | 124 ++++ .../PooledAwait/Internal/StateMachineBox.cs | 113 ++++ .../PooledAwait/Internal/TaskUtils.cs | 171 +++++ .../PooledAwait/Internal/ThrowHelper.cs | 30 + .../PooledAwait/LazyTaskCompletionSource.cs | 127 ++++ .../PooledAwait/LazyTaskCompletionSourceT.cs | 126 ++++ .../FireAndForgetMethodBuilder.cs | 73 +++ .../MethodBuilders/PooledTaskMethodBuilder.cs | 105 ++++ .../PooledTaskMethodBuilderT.cs | 105 ++++ .../PooledValueTaskMethodBuilder.cs | 94 +++ .../PooledValueTaskMethodBuilderT.cs | 95 +++ .../PooledAwait/Pool.cs | 73 +++ .../PooledAwait/PoolSizeAttribute.cs | 21 + .../PooledAwait/PoolT.cs | 47 ++ .../PooledAwait/PooledAwait.csproj | 29 + .../PooledAwait/PooledTask.cs | 60 ++ .../PooledAwait/PooledTaskT.cs | 60 ++ .../PooledAwait/PooledValueTask.cs | 71 +++ .../PooledAwait/PooledValueTaskSource.cs | 126 ++++ .../PooledAwait/PooledValueTaskSourceT.cs | 141 +++++ .../PooledAwait/PooledValueTaskT.cs | 85 +++ .../PooledAwait/Properties/AssemblyInfo.cs | 5 + .../PooledAwait/ValueTaskCompletionSourceT.cs | 218 +++++++ .../ThingsGateway.NewLife.X.csproj | 21 +- .../Threading/TimerScheduler.cs | 130 ++-- src/Directory.Build.props | 8 +- .../Channel/Plugin/PluginUtil.cs | 28 +- .../Device/DeviceBase.cs | 145 +++-- .../Device/DeviceExtension.cs | 94 ++- .../Common/Tasks/CronScheduledTask.cs | 86 +-- .../Common/Tasks/ScheduledAsyncTask.cs | 88 +-- .../Driver/Collect/CollectBase.cs | 594 +++++++++--------- .../Driver/Collect/CollectFoundationBase.cs | 268 ++++---- .../Benchmark/SemaphoreBenchmark.cs | 94 +++ .../Benchmark/TimeoutBenchmark.cs | 4 +- .../Program.cs | 6 +- src/ThingsGateway.sln | 6 +- 53 files changed, 4127 insertions(+), 698 deletions(-) create mode 100644 src/Admin/ThingsGateway.NewLife.X/Collections/ObjectPoolLock.cs create mode 100644 src/Admin/ThingsGateway.NewLife.X/Collections/ValueStopwatch.cs create mode 100644 src/Admin/ThingsGateway.NewLife.X/PooledAwait/AwaitableExtensions.cs create mode 100644 src/Admin/ThingsGateway.NewLife.X/PooledAwait/ConfiguredYieldAwaitable.cs create mode 100644 src/Admin/ThingsGateway.NewLife.X/PooledAwait/FireAndForget.cs create mode 100644 src/Admin/ThingsGateway.NewLife.X/PooledAwait/IResettable.cs create mode 100644 src/Admin/ThingsGateway.NewLife.X/PooledAwait/Internal/BrowsableAttribute.cs create mode 100644 src/Admin/ThingsGateway.NewLife.X/PooledAwait/Internal/Counters.cs create mode 100644 src/Admin/ThingsGateway.NewLife.X/PooledAwait/Internal/LazyTaskStateT.cs create mode 100644 src/Admin/ThingsGateway.NewLife.X/PooledAwait/Internal/ManualResetValueTaskSourceCore.cs create mode 100644 src/Admin/ThingsGateway.NewLife.X/PooledAwait/Internal/Nothing.cs create mode 100644 src/Admin/ThingsGateway.NewLife.X/PooledAwait/Internal/PooledStateT.cs create mode 100644 src/Admin/ThingsGateway.NewLife.X/PooledAwait/Internal/StateMachineBox.cs create mode 100644 src/Admin/ThingsGateway.NewLife.X/PooledAwait/Internal/TaskUtils.cs create mode 100644 src/Admin/ThingsGateway.NewLife.X/PooledAwait/Internal/ThrowHelper.cs create mode 100644 src/Admin/ThingsGateway.NewLife.X/PooledAwait/LazyTaskCompletionSource.cs create mode 100644 src/Admin/ThingsGateway.NewLife.X/PooledAwait/LazyTaskCompletionSourceT.cs create mode 100644 src/Admin/ThingsGateway.NewLife.X/PooledAwait/MethodBuilders/FireAndForgetMethodBuilder.cs create mode 100644 src/Admin/ThingsGateway.NewLife.X/PooledAwait/MethodBuilders/PooledTaskMethodBuilder.cs create mode 100644 src/Admin/ThingsGateway.NewLife.X/PooledAwait/MethodBuilders/PooledTaskMethodBuilderT.cs create mode 100644 src/Admin/ThingsGateway.NewLife.X/PooledAwait/MethodBuilders/PooledValueTaskMethodBuilder.cs create mode 100644 src/Admin/ThingsGateway.NewLife.X/PooledAwait/MethodBuilders/PooledValueTaskMethodBuilderT.cs create mode 100644 src/Admin/ThingsGateway.NewLife.X/PooledAwait/Pool.cs create mode 100644 src/Admin/ThingsGateway.NewLife.X/PooledAwait/PoolSizeAttribute.cs create mode 100644 src/Admin/ThingsGateway.NewLife.X/PooledAwait/PoolT.cs create mode 100644 src/Admin/ThingsGateway.NewLife.X/PooledAwait/PooledAwait.csproj create mode 100644 src/Admin/ThingsGateway.NewLife.X/PooledAwait/PooledTask.cs create mode 100644 src/Admin/ThingsGateway.NewLife.X/PooledAwait/PooledTaskT.cs create mode 100644 src/Admin/ThingsGateway.NewLife.X/PooledAwait/PooledValueTask.cs create mode 100644 src/Admin/ThingsGateway.NewLife.X/PooledAwait/PooledValueTaskSource.cs create mode 100644 src/Admin/ThingsGateway.NewLife.X/PooledAwait/PooledValueTaskSourceT.cs create mode 100644 src/Admin/ThingsGateway.NewLife.X/PooledAwait/PooledValueTaskT.cs create mode 100644 src/Admin/ThingsGateway.NewLife.X/PooledAwait/Properties/AssemblyInfo.cs create mode 100644 src/Admin/ThingsGateway.NewLife.X/PooledAwait/ValueTaskCompletionSourceT.cs create mode 100644 src/Plugin/ThingsGateway.Foundation.Benchmark/Benchmark/SemaphoreBenchmark.cs diff --git a/src/Admin/ThingsGateway.NewLife.X/Collections/NonBlockingDictionary/ConcurrentDictionary/DictionaryImpl`2.cs b/src/Admin/ThingsGateway.NewLife.X/Collections/NonBlockingDictionary/ConcurrentDictionary/DictionaryImpl`2.cs index 7e007e656..5392b03d1 100644 --- a/src/Admin/ThingsGateway.NewLife.X/Collections/NonBlockingDictionary/ConcurrentDictionary/DictionaryImpl`2.cs +++ b/src/Admin/ThingsGateway.NewLife.X/Collections/NonBlockingDictionary/ConcurrentDictionary/DictionaryImpl`2.cs @@ -4,7 +4,6 @@ #nullable disable -using System.Collections; using System.Runtime.CompilerServices; namespace System.Collections.Concurrent diff --git a/src/Admin/ThingsGateway.NewLife.X/Collections/NonBlockingDictionary/NonBlockingDictionary.cs b/src/Admin/ThingsGateway.NewLife.X/Collections/NonBlockingDictionary/NonBlockingDictionary.cs index 6a44759da..5abcb6ab7 100644 --- a/src/Admin/ThingsGateway.NewLife.X/Collections/NonBlockingDictionary/NonBlockingDictionary.cs +++ b/src/Admin/ThingsGateway.NewLife.X/Collections/NonBlockingDictionary/NonBlockingDictionary.cs @@ -5,12 +5,10 @@ #nullable disable #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.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Runtime.CompilerServices; -using System.Collections.Concurrent; using static System.Collections.Concurrent.DictionaryImpl; diff --git a/src/Admin/ThingsGateway.NewLife.X/Collections/ObjectPool.cs b/src/Admin/ThingsGateway.NewLife.X/Collections/ObjectPool.cs index 6e5f9869b..15d97bff8 100644 --- a/src/Admin/ThingsGateway.NewLife.X/Collections/ObjectPool.cs +++ b/src/Admin/ThingsGateway.NewLife.X/Collections/ObjectPool.cs @@ -31,8 +31,8 @@ public class ObjectPool : DisposeBase, IPool where T : notnull /// 最小个数。默认1 public Int32 Min { get; set; } = 1; - /// 空闲清理时间。最小个数之上的资源超过空闲时间时被清理,默认10s - public Int32 IdleTime { get; set; } = 10; + /// 空闲清理时间。最小个数之上的资源超过空闲时间时被清理,默认60s + public Int32 IdleTime { get; set; } = 60; /// 完全空闲清理时间。最小个数之下的资源超过空闲时间时被清理,默认0s永不清理 public Int32 AllIdleTime { get; set; } = 0; @@ -126,9 +126,7 @@ public class ObjectPool : DisposeBase, IPool where T : notnull if (Max > 0 && count >= Max) { var msg = $"申请失败,已有 {count:n0} 达到或超过最大值 {Max:n0}"; - WriteLog("Acquire Max " + msg); - throw new Exception(Name + " " + msg); } @@ -268,7 +266,7 @@ public class ObjectPool : DisposeBase, IPool where T : notnull { 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 : DisposeBase, IPool where T : notnull var count = 0; // 清理过期不还。避免有借没还 - if (!_busy.IsEmpty) + if (AllIdleTime > 0 && !_busy.IsEmpty) { var exp = TimerX.Now.AddSeconds(-AllIdleTime); foreach (var item in _busy) diff --git a/src/Admin/ThingsGateway.NewLife.X/Collections/ObjectPoolLock.cs b/src/Admin/ThingsGateway.NewLife.X/Collections/ObjectPoolLock.cs new file mode 100644 index 000000000..d05be2027 --- /dev/null +++ b/src/Admin/ThingsGateway.NewLife.X/Collections/ObjectPoolLock.cs @@ -0,0 +1,262 @@ +using ThingsGateway.NewLife.Log; +using ThingsGateway.NewLife.Reflection; + +namespace ThingsGateway.NewLife.Collections; + +/// 资源池。支持空闲释放,主要用于数据库连接池和网络连接池 +/// +/// 文档 https://newlifex.com/core/object_pool +/// +/// +public class ObjectPoolLock : DisposeBase, IPool where T : class +{ + #region 属性 + /// 名称 + public String Name { get; set; } + + private Int32 _FreeCount; + /// 空闲个数 + public Int32 FreeCount => _FreeCount; + + private Int32 _BusyCount; + /// 繁忙个数 + public Int32 BusyCount => _BusyCount; + + /// 最大个数。默认0,0表示无上限 + public Int32 Max { get; set; } = 0; + + /// 最小个数。默认1 + public Int32 Min { get; set; } = 1; + + private readonly object _syncRoot = new(); + + /// 基础空闲集合。只保存最小个数,最热部分 + private readonly Stack _free = new(); + + /// 扩展空闲集合。保存最小个数以外部分 + private readonly Queue _free2 = new(); + + /// 借出去的放在这 + private readonly HashSet _busy = new(); + + //private readonly Object SyncRoot = new(); + #endregion + + #region 构造 + /// 实例化一个资源池 + 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(); + } + /// 销毁 + /// + 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} Min={Min} Max={Max}"); + } + } + #endregion + + #region 主方法 + /// 借出 + /// + public virtual T Get() + { + T? pi = null; + do + { + lock (_syncRoot) + { + if (_free.Count > 0) + { + pi = _free.Pop(); + _FreeCount--; + } + else if (_free2.Count > 0) + { + pi = _free2.Dequeue(); + _FreeCount--; + } + else + { + var count = BusyCount; + if (Max > 0 && count >= Max) + { + var msg = $"申请失败,已有 {count:n0} 达到或超过最大值 {Max:n0}"; + WriteLog("Acquire Max " + msg); + throw new Exception(Name + " " + msg); + } + + pi = OnCreate(); + if (count == 0) Init(); + +#if DEBUG + WriteLog("Acquire Create Free={0} Busy={1}", FreeCount, count + 1); +#endif + } + } + + // 如果拿到的对象不可用,则重新借 + } while (pi == null || !OnGet(pi)); + + lock (_syncRoot) + { + // 加入繁忙集合 + _busy.Add(pi); + + _BusyCount++; + } + return pi; + } + + /// 借出时是否可用 + /// + /// + protected virtual Boolean OnGet(T value) => true; + + /// 申请资源包装项,Dispose时自动归还到池中 + /// + public PoolItem GetItem() => new(this, Get()); + + /// 归还 + /// + 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) + { + if (_FreeCount < Min) + _free.Push(value); + else + _free2.Enqueue(value); + _FreeCount++; + } + + return true; + } + + /// 归还时是否可用 + /// + /// + protected virtual Boolean OnReturn(T value) => true; + + /// 清空已有对象 + 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); + } + + while (_free2.Count > 0) + { + var pi = _free2.Dequeue(); + OnDispose(pi); + } + + _FreeCount = 0; + + foreach (var item in _busy) + { + OnDispose(item); + } + _busy.Clear(); + _BusyCount = 0; + } + + return count; + } + + /// 销毁 + /// + protected virtual void OnDispose(T? value) => value.TryDispose(); + #endregion + + #region 重载 + /// 创建实例 + /// + protected virtual T? OnCreate() => (T?)typeof(T).CreateInstance(); + #endregion + protected object lockThis = new(); + + #region 日志 + /// 日志 + public ILog Log { get; set; } = Logger.Null; + + /// 写日志 + /// + /// + public void WriteLog(String format, params Object?[] args) + { + if (Log?.Enable != true) return; + + Log.Info(Name + "." + format, args); + } + #endregion +} diff --git a/src/Admin/ThingsGateway.NewLife.X/Collections/ValueStopwatch.cs b/src/Admin/ThingsGateway.NewLife.X/Collections/ValueStopwatch.cs new file mode 100644 index 000000000..07f716cf2 --- /dev/null +++ b/src/Admin/ThingsGateway.NewLife.X/Collections/ValueStopwatch.cs @@ -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); + } +} \ No newline at end of file diff --git a/src/Admin/ThingsGateway.NewLife.X/Common/ExpiringDictionary.cs b/src/Admin/ThingsGateway.NewLife.X/Common/ExpiringDictionary.cs index 5e3a70b8c..0ad23d583 100644 --- a/src/Admin/ThingsGateway.NewLife.X/Common/ExpiringDictionary.cs +++ b/src/Admin/ThingsGateway.NewLife.X/Common/ExpiringDictionary.cs @@ -62,7 +62,7 @@ public class ExpiringDictionary : IDisposable this.comparer = comparer; _dict = new NonBlockingDictionary(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) diff --git a/src/Admin/ThingsGateway.NewLife.X/Common/WaitLock.cs b/src/Admin/ThingsGateway.NewLife.X/Common/WaitLock.cs index 43854ce84..2fb531a75 100644 --- a/src/Admin/ThingsGateway.NewLife.X/Common/WaitLock.cs +++ b/src/Admin/ThingsGateway.NewLife.X/Common/WaitLock.cs @@ -90,15 +90,50 @@ public sealed class WaitLock : IDisposable /// 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); + else + return _waiterLock.WaitAsync(Timeout.Infinite, CancellationToken.None); + +#else + return _waiterLock.WaitAsync(Timeout.Infinite,cancellationToken); +#endif } +#if NET6_0_OR_GREATER + /// Performs the asynchronous wait. + /// The asynchronous waiter. + /// The timeout. + /// The cancellation token. + /// The task to return to the caller. + private Task WaitUntilCountOrTimeoutAsync(Task asyncWaiter, int millisecondsTimeout, CancellationToken cancellationToken) + { + if (millisecondsTimeout == Timeout.Infinite) + { + return (asyncWaiter.WaitAsync(cancellationToken)); + } + else + { + return (asyncWaiter.WaitAsync(TimeSpan.FromMilliseconds(millisecondsTimeout), cancellationToken)); + } + } +#endif + /// /// 进入锁 /// public Task WaitAsync(int millisecondsTimeout, CancellationToken cancellationToken = default) { - return _waiterLock.WaitAsync(millisecondsTimeout, cancellationToken); +#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); +#endif } bool DisposedValue; diff --git a/src/Admin/ThingsGateway.NewLife.X/PooledAwait/AwaitableExtensions.cs b/src/Admin/ThingsGateway.NewLife.X/PooledAwait/AwaitableExtensions.cs new file mode 100644 index 000000000..ad7702afc --- /dev/null +++ b/src/Admin/ThingsGateway.NewLife.X/PooledAwait/AwaitableExtensions.cs @@ -0,0 +1,15 @@ +using System.Runtime.CompilerServices; + +namespace PooledAwait +{ + /// + /// Provides async/await-related extension methods + /// + public static class AwaitableExtensions + { + /// Controls whether a yield operation should respect captured context + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static ConfiguredYieldAwaitable ConfigureAwait(this YieldAwaitable _, bool continueOnCapturedContext) + => new ConfiguredYieldAwaitable(continueOnCapturedContext); + } +} diff --git a/src/Admin/ThingsGateway.NewLife.X/PooledAwait/ConfiguredYieldAwaitable.cs b/src/Admin/ThingsGateway.NewLife.X/PooledAwait/ConfiguredYieldAwaitable.cs new file mode 100644 index 000000000..60977baa2 --- /dev/null +++ b/src/Admin/ThingsGateway.NewLife.X/PooledAwait/ConfiguredYieldAwaitable.cs @@ -0,0 +1,125 @@ +using System.Runtime.CompilerServices; + +namespace PooledAwait +{ + /// Provides an awaitable context for switching into a target environment. + public readonly struct ConfiguredYieldAwaitable + { + /// + public override bool Equals(object? obj) => obj is ConfiguredYieldAwaitable other && other._continueOnCapturedContext == _continueOnCapturedContext; + /// + public override int GetHashCode() => _continueOnCapturedContext ? 1 : 0; + /// + public override string ToString() => nameof(ConfiguredYieldAwaitable); + + private readonly bool _continueOnCapturedContext; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal ConfiguredYieldAwaitable(bool continueOnCapturedContext) + => _continueOnCapturedContext = continueOnCapturedContext; + + /// Gets an awaiter for this . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public ConfiguredYieldAwaiter GetAwaiter() + => new ConfiguredYieldAwaiter(_continueOnCapturedContext); + + /// Provides an awaitable context for switching into a target environment. + public readonly struct ConfiguredYieldAwaiter : ICriticalNotifyCompletion, INotifyCompletion + { + /// + public override bool Equals(object? obj) => obj is ConfiguredYieldAwaiter other && other._continueOnCapturedContext == _continueOnCapturedContext; + /// + public override int GetHashCode() => _continueOnCapturedContext ? 1 : 0; + /// + public override string ToString() => nameof(ConfiguredYieldAwaiter); + + private readonly bool _continueOnCapturedContext; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal ConfiguredYieldAwaiter(bool continueOnCapturedContext) + => _continueOnCapturedContext = continueOnCapturedContext; + + /// Gets whether a yield is not required. + public bool IsCompleted + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => false; + } + + /// Ends the await operation. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void GetResult() { } + + /// Posts the back to the current context. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void OnCompleted(Action continuation) + { + if (_continueOnCapturedContext) YieldFlowContext(continuation, true); + else YieldNoContext(continuation, true); + } + + /// Posts the back to the current context. + [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.TryGet() ?? new ContinuationWorkItem(); + box._continuation = continuation; + return box; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + void IThreadPoolWorkItem.Execute() + { + var callback = _continuation; + _continuation = null; + Pool.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 + } + } + } + } +} diff --git a/src/Admin/ThingsGateway.NewLife.X/PooledAwait/FireAndForget.cs b/src/Admin/ThingsGateway.NewLife.X/PooledAwait/FireAndForget.cs new file mode 100644 index 000000000..3d8680c4b --- /dev/null +++ b/src/Admin/ThingsGateway.NewLife.X/PooledAwait/FireAndForget.cs @@ -0,0 +1,62 @@ +using PooledAwait.Internal; + +using System.Runtime.CompilerServices; + +namespace PooledAwait +{ + /// + /// Represents an operation that completes at the first incomplete await, + /// with the remainder continuing in the background + /// + [AsyncMethodBuilder(typeof(MethodBuilders.FireAndForgetMethodBuilder))] + public readonly struct FireAndForget + { + /// + public override bool Equals(object? obj) => ThrowHelper.ThrowNotSupportedException(); + /// + public override int GetHashCode() => ThrowHelper.ThrowNotSupportedException(); + /// + public override string ToString() => nameof(FireAndForget); + + /// + /// Raised when exceptions occur on fire-and-forget methods + /// + public static event Action? Exception; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static void OnException(Exception exception) + { + if (exception != null) Exception?.Invoke(exception); + } + + /// + /// Gets the instance as a value-task + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public ValueTask AsValueTask() => default; + + /// + /// Gets the instance as a task + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Task AsTask() => TaskUtils.CompletedTask; + + /// + /// Gets the instance as a task + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static implicit operator Task(FireAndForget _) => TaskUtils.CompletedTask; + + /// + /// Gets the instance as a value-task + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static implicit operator ValueTask(FireAndForget _) => default; + + /// + /// Gets the awaiter for the instance + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public ValueTaskAwaiter GetAwaiter() => default(ValueTask).GetAwaiter(); + } +} diff --git a/src/Admin/ThingsGateway.NewLife.X/PooledAwait/IResettable.cs b/src/Admin/ThingsGateway.NewLife.X/PooledAwait/IResettable.cs new file mode 100644 index 000000000..396e604fa --- /dev/null +++ b/src/Admin/ThingsGateway.NewLife.X/PooledAwait/IResettable.cs @@ -0,0 +1,13 @@ +namespace PooledAwait +{ + /// + /// Indicates that an object can be reset + /// + public interface IResettable + { + /// + /// Resets this instance + /// + public void Reset(); + } +} diff --git a/src/Admin/ThingsGateway.NewLife.X/PooledAwait/Internal/BrowsableAttribute.cs b/src/Admin/ThingsGateway.NewLife.X/PooledAwait/Internal/BrowsableAttribute.cs new file mode 100644 index 000000000..6576ac1ff --- /dev/null +++ b/src/Admin/ThingsGateway.NewLife.X/PooledAwait/Internal/BrowsableAttribute.cs @@ -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 \ No newline at end of file diff --git a/src/Admin/ThingsGateway.NewLife.X/PooledAwait/Internal/Counters.cs b/src/Admin/ThingsGateway.NewLife.X/PooledAwait/Internal/Counters.cs new file mode 100644 index 000000000..d0707c991 --- /dev/null +++ b/src/Admin/ThingsGateway.NewLife.X/PooledAwait/Internal/Counters.cs @@ -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(); + public override int GetHashCode() => ThrowHelper.ThrowNotSupportedException(); + 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}"; + } +} diff --git a/src/Admin/ThingsGateway.NewLife.X/PooledAwait/Internal/LazyTaskStateT.cs b/src/Admin/ThingsGateway.NewLife.X/PooledAwait/Internal/LazyTaskStateT.cs new file mode 100644 index 000000000..80a74a1bc --- /dev/null +++ b/src/Admin/ThingsGateway.NewLife.X/PooledAwait/Internal/LazyTaskStateT.cs @@ -0,0 +1,173 @@ +using System.Runtime.CompilerServices; + +namespace PooledAwait.Internal +{ + internal sealed class LazyTaskState + { + private short _version; + private T _result; + private Exception? _exception; + private Task? _task; + private bool _isComplete; + private ValueTaskCompletionSource _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.Canceled; + else if (_exception != null) _task = TaskUtils.FromException(_exception); + else if (_isComplete) _task = typeof(T) == typeof(Nothing) ? TaskUtils.CompletedTask : TaskUtils.TaskFactory.FromResult(_result); + else + { + _source = ValueTaskCompletionSource.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.Canceled; + return true; + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static LazyTaskState Create() => Pool>.TryGet() ?? new LazyTaskState(); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private LazyTaskState() + { + Counters.LazyStateAllocated.Increment(); + _result = default!; + _version = InitialVersion; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static LazyTaskState CreateConstant(T value) + { + var obj = new LazyTaskState + { + _version = Constant + }; + obj.TrySetResult(Constant, value); + return obj; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static LazyTaskState CreateCanceled() + { + var obj = new LazyTaskState + { + _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>.TryPut(this); + break; + } + } + } + } + finally + { + if (haveLock) Monitor.Exit(this); + } + } + } +} diff --git a/src/Admin/ThingsGateway.NewLife.X/PooledAwait/Internal/ManualResetValueTaskSourceCore.cs b/src/Admin/ThingsGateway.NewLife.X/PooledAwait/Internal/ManualResetValueTaskSourceCore.cs new file mode 100644 index 000000000..758832f1f --- /dev/null +++ b/src/Admin/ThingsGateway.NewLife.X/PooledAwait/Internal/ManualResetValueTaskSourceCore.cs @@ -0,0 +1,313 @@ +#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? Continuation; + public readonly object? State; + private WaitCallbackShim(Action? continuation, object? state) + { + Continuation = continuation; + State = state; + } + public static object Create(Action? continuation, object? state) + => Pool.Box(new WaitCallbackShim(continuation, state)); + + private void InvokeContinuation() => Continuation?.Invoke(State); + + public static readonly WaitCallback Invoke = state => Pool.UnboxAndReturn(state).InvokeContinuation(); + } + + /// Provides the core logic for implementing a manual-reset or . + /// + [StructLayout(LayoutKind.Auto)] + public struct ManualResetValueTaskSourceCore + { + /// + /// The callback to invoke when the operation completes if was called before the operation completed, + /// or 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. + /// + private Action? _continuation; + /// State to pass to . + private object? _continuationState; + /// to flow to the callback, or null if no flowing is required. + private ExecutionContext? _executionContext; + /// + /// A "captured" or with which to invoke the callback, + /// or null if no special context is required. + /// + private object? _capturedContext; + /// Whether the current operation has completed. + private bool _completed; + /// The result with which the operation succeeded, or the default value if it hasn't yet completed or failed. + /* [AllowNull, MaybeNull] */ private TResult _result; + /// The exception with which the operation failed, or null if it hasn't yet completed or completed successfully. + private ExceptionDispatchInfo? _error; + /// The current version of this value, used to help prevent misuse. + private short _version; + + /// Gets or sets whether to force continuations to run asynchronously. + /// Continuations may run asynchronously if this is false, but they'll never run synchronously if this is true. + public bool RunContinuationsAsynchronously { get; set; } + + /// Resets to prepare for the next operation. + 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; + } + + /// Completes with a successful result. + /// The result. + public void SetResult(TResult result) + { + _result = result; + SignalCompletion(); + } + + /// Complets with an error. + /// + public void SetException(Exception error) + { + _error = ExceptionDispatchInfo.Capture(error); + SignalCompletion(); + } + + /// Gets the operation version. + public short Version => _version; + + /// Gets the status of the operation. + /// Opaque value that was provided to the 's constructor. + 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; + } + + /// Gets the result of the operation. + /// Opaque value that was provided to the 's constructor. + // [StackTraceHidden] + public TResult GetResult(short token) + { + ValidateToken(token); + if (!_completed) + { + ThrowHelper.ThrowInvalidOperationException(); + } + + _error?.Throw(); + return _result; + } + + /// Schedules the continuation action for this operation. + /// The continuation to invoke when the operation has completed. + /// The state object to pass to when it's invoked. + /// Opaque value that was provided to the 's constructor. + /// The flags describing the behavior of the continuation. + public void OnCompleted(Action 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, 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; + } + } + } + + /// Ensures that the specified token matches the current version. + /// The token supplied by . + private void ValidateToken(short token) + { + if (token != _version) + { + ThrowHelper.ThrowInvalidOperationException(); + } + } + + /// Signals that the operation has completed. Invoked after the result or error has been set. + 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>(this)); + } + else + { + InvokeContinuation(); + } + } + } + + static readonly ContextCallback s_UnboxAndInvokeContextCallback = state => Pool.UnboxAndReturn>(state).InvokeContinuation(); + + /// + /// Invokes the continuation with the appropriate captured context / scheduler. + /// This assumes that if is not null we're already + /// running within that . + /// + 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, 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 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 diff --git a/src/Admin/ThingsGateway.NewLife.X/PooledAwait/Internal/Nothing.cs b/src/Admin/ThingsGateway.NewLife.X/PooledAwait/Internal/Nothing.cs new file mode 100644 index 000000000..f754a27f1 --- /dev/null +++ b/src/Admin/ThingsGateway.NewLife.X/PooledAwait/Internal/Nothing.cs @@ -0,0 +1,9 @@ +namespace PooledAwait.Internal +{ + internal readonly struct Nothing // to express ValueTask via PooledState + { + public override string ToString() => nameof(Nothing); + public override int GetHashCode() => 0; + public override bool Equals(object? obj) => obj is Nothing; + } +} diff --git a/src/Admin/ThingsGateway.NewLife.X/PooledAwait/Internal/PooledStateT.cs b/src/Admin/ThingsGateway.NewLife.X/PooledAwait/Internal/PooledStateT.cs new file mode 100644 index 000000000..6f94630c5 --- /dev/null +++ b/src/Admin/ThingsGateway.NewLife.X/PooledAwait/Internal/PooledStateT.cs @@ -0,0 +1,124 @@ +using System.Runtime.CompilerServices; +using System.Threading.Tasks.Sources; + +namespace PooledAwait.Internal +{ + internal sealed class PooledState : IValueTaskSource, IValueTaskSource + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static PooledState Create(out short token) + { + var obj = Pool>.TryGet() ?? new PooledState(); + 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 _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>.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 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); + } +} diff --git a/src/Admin/ThingsGateway.NewLife.X/PooledAwait/Internal/StateMachineBox.cs b/src/Admin/ThingsGateway.NewLife.X/PooledAwait/Internal/StateMachineBox.cs new file mode 100644 index 000000000..f0d9d73a5 --- /dev/null +++ b/src/Admin/ThingsGateway.NewLife.X/PooledAwait/Internal/StateMachineBox.cs @@ -0,0 +1,113 @@ +using System.Runtime.CompilerServices; + +namespace PooledAwait.Internal +{ + internal sealed class StateMachineBox +#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 Create(ref TStateMachine stateMachine) + { + var box = Pool>.TryGet() ?? new StateMachineBox(); + box._stateMachine = stateMachine; + return box; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void AwaitOnCompleted( + 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( + 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 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?)state!)?.Execute(); + static readonly WaitCallback s_WaitCallback = state => ((StateMachineBox?)state)?.Execute(); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Execute() + { + // extract the state + var tmp = _stateMachine; + + // recycle the instance + _stateMachine = default!; + Pool>.TryPut(this); + Counters.StateMachineBoxRecycled.Increment(); + + // progress the state machine + tmp.MoveNext(); + } + } +} diff --git a/src/Admin/ThingsGateway.NewLife.X/PooledAwait/Internal/TaskUtils.cs b/src/Admin/ThingsGateway.NewLife.X/PooledAwait/Internal/TaskUtils.cs new file mode 100644 index 000000000..7f8b518b6 --- /dev/null +++ b/src/Admin/ThingsGateway.NewLife.X/PooledAwait/Internal/TaskUtils.cs @@ -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().Version; + + public static readonly TaskCanceledException SharedTaskCanceledException = new TaskCanceledException(); +#if NET45 + public static readonly Task CompletedTask = TaskFactory.FromResult(default); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Task FromException(Exception exception) + { + var source = ValueTaskCompletionSource.Create(); + source.TrySetException(exception); + return source.Task; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Task FromException(Exception exception) => FromException(exception); +#else + public static readonly Task CompletedTask = Task.CompletedTask; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Task FromException(Exception exception) => Task.FromException(exception); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Task FromException(Exception exception) => Task.FromException(exception); +#endif + + + internal static class TaskFactory + { + // draws from AsyncMethodBuilder, but less boxing + + public static readonly Task Canceled = CreateCanceled(); + + static Task CreateCanceled() + { + var source = ValueTaskCompletionSource.Create(); + source.TrySetCanceled(); + return source.Task; + } + + private static readonly TaskCache _cache = (TaskCache)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(); + + if (typeof(TResult) == typeof(string)) return new StringTaskCache(); +#if !NETSTANDARD1_3 + if (!typeof(TResult).IsValueType) return new ObjectTaskCache(); +#endif + return new TaskCache(); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Task FromResult(TResult result) => _cache.FromResult(result); + } + + class TaskCache + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal virtual Task FromResult(TResult value) => Task.FromResult(value); + } + class NothingTaskCache : TaskCache + { + private static readonly Task s_Instance = Task.FromResult(default); + internal override Task FromResult(Nothing value) => s_Instance; + } + class DefaultEquatableTaskCache : TaskCache + { + private static readonly Task s_Default = Task.FromResult(default!); + private static readonly EqualityComparer _comparer = EqualityComparer.Default; + internal override Task FromResult(TResult value) + => _comparer.Equals(value, default!) ? s_Default : base.FromResult(value); + } + class ObjectTaskCache : TaskCache + { + private static readonly Task s_Null = Task.FromResult(default!); + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal override Task FromResult(TResult value) + => value == null ? s_Null : base.FromResult(value); + } + sealed class StringTaskCache : ObjectTaskCache + { + private static readonly Task s_Empty = Task.FromResult(""); + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal override Task FromResult(string value) + => string.IsNullOrEmpty(value) ? s_Empty : base.FromResult(value); + } + sealed class BooleanTaskCache : TaskCache + { + static readonly Task s_True = Task.FromResult(true), s_False = Task.FromResult(false); + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal override Task FromResult(bool value) => value ? s_True : s_False; + } + sealed class NullableBooleanTaskCache : TaskCache + { + static readonly Task s_True = Task.FromResult((bool?)true), s_False = Task.FromResult((bool?)false), + s_Null = Task.FromResult((bool?)null); + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal override Task FromResult(bool? value) => + value.HasValue ? (value.GetValueOrDefault() ? s_True : s_False) : s_Null; + } + sealed class Int32TaskCache : TaskCache + { + const int MIN_INC = -1, MAX_EXC = 11; + static readonly Task[] s_Known = CreateKnown(); + static Task[] CreateKnown() + { + var arr = new Task[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 FromResult(int value) + => value >= MIN_INC && value < MAX_EXC ? s_Known[value - MIN_INC] : base.FromResult(value); + } + sealed class NullableInt32TaskCache : TaskCache + { + const int MIN_INC = -1, MAX_EXC = 11; + static readonly Task[] s_Known = CreateKnown(); + static readonly Task s_Null = Task.FromResult((int?)null); + static Task[] CreateKnown() + { + var arr = new Task[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 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; + } + } + + } +} diff --git a/src/Admin/ThingsGateway.NewLife.X/PooledAwait/Internal/ThrowHelper.cs b/src/Admin/ThingsGateway.NewLife.X/PooledAwait/Internal/ThrowHelper.cs new file mode 100644 index 000000000..c38294afe --- /dev/null +++ b/src/Admin/ThingsGateway.NewLife.X/PooledAwait/Internal/ThrowHelper.cs @@ -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(string? message = null) + { + ThrowInvalidOperationException(message); + return default!; + } + + [MethodImpl(MethodImplOptions.NoInlining)] + internal static T ThrowNotSupportedException() => 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(); + } +} diff --git a/src/Admin/ThingsGateway.NewLife.X/PooledAwait/LazyTaskCompletionSource.cs b/src/Admin/ThingsGateway.NewLife.X/PooledAwait/LazyTaskCompletionSource.cs new file mode 100644 index 000000000..a44cd1219 --- /dev/null +++ b/src/Admin/ThingsGateway.NewLife.X/PooledAwait/LazyTaskCompletionSource.cs @@ -0,0 +1,127 @@ +using PooledAwait.Internal; + +using System.Runtime.CompilerServices; + +namespace PooledAwait +{ + /// + /// 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 + /// + public readonly struct LazyTaskCompletionSource : IDisposable + { + + private static LazyTaskCompletionSource _completed, _canceled; + + /// + /// A global LazyTaskCompletionSource that represents a completed operation + /// + public static LazyTaskCompletionSource CompletedTask + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => _completed.IsValid ? _completed : _completed = new LazyTaskCompletionSource(LazyTaskState.CreateConstant(default)); + } + + /// + /// A global LazyTaskCompletionSource that represents a cancelled operation + /// + public static LazyTaskCompletionSource CanceledTask + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => _canceled.IsValid ? _canceled : _canceled = new LazyTaskCompletionSource(LazyTaskState.CreateCanceled()); + } + + /// + public override bool Equals(object? obj) => obj is LazyTaskCompletionSource ltcs && _state == ltcs._state && _token == ltcs._token; + /// + public override int GetHashCode() => (_state == null ? 0 : _state.GetHashCode()) ^ _token; + /// + public override string ToString() => nameof(LazyTaskCompletionSource); + + private readonly LazyTaskState _state; + private readonly short _token; + + /// + /// Gets the task associated with this instance + /// + public Task Task => _state?.GetTask(_token) ?? ThrowHelper.ThrowInvalidOperationException(); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private LazyTaskCompletionSource(LazyTaskState state) + { + _state = state; + _token = state.Version; + } + + /// + /// Create a new instance; this instance should be disposed when it is known to be unwanted + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static LazyTaskCompletionSource Create() + => new LazyTaskCompletionSource(LazyTaskState.Create()); + + /// + /// Set the result of the operation + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool TrySetResult() => _state?.TrySetResult(_token, default) == true; + + /// + /// Set the result of the operation + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool TrySetCanceled(CancellationToken cancellationToken = default) => _state?.TrySetCanceled(_token, cancellationToken) == true; + + /// + /// Set the result of the operation + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void SetResult() + { + if (!TrySetResult()) ThrowHelper.ThrowInvalidOperationException(); + } + + /// + /// Set the result of the operation + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void SetCanceled(CancellationToken cancellationToken = default) + { + if (!TrySetCanceled(cancellationToken)) ThrowHelper.ThrowInvalidOperationException(); + } + + /// + /// Set the result of the operation + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool TrySetException(Exception exception) => _state?.TrySetException(_token, exception) == true; + + /// + /// Set the result of the operation + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void SetException(Exception exception) + { + if (!TrySetException(exception)) ThrowHelper.ThrowInvalidOperationException(); + } + + /// + /// Release all resources associated with this operation + /// + [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; + + /// + /// Indicates whether this is an invalid default instance + /// + public bool IsNull + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => _state == null; + } + } +} diff --git a/src/Admin/ThingsGateway.NewLife.X/PooledAwait/LazyTaskCompletionSourceT.cs b/src/Admin/ThingsGateway.NewLife.X/PooledAwait/LazyTaskCompletionSourceT.cs new file mode 100644 index 000000000..8c310b423 --- /dev/null +++ b/src/Admin/ThingsGateway.NewLife.X/PooledAwait/LazyTaskCompletionSourceT.cs @@ -0,0 +1,126 @@ +using PooledAwait.Internal; + +using System.Runtime.CompilerServices; + +namespace PooledAwait +{ + /// + /// 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 + /// + public readonly struct LazyTaskCompletionSource : IDisposable + { + /// + public override bool Equals(object? obj) => obj is LazyTaskCompletionSource ltcs && _state == ltcs._state && _token == ltcs._token; + /// + public override int GetHashCode() => (_state == null ? 0 : _state.GetHashCode()) ^ _token; + /// + public override string ToString() => nameof(LazyTaskCompletionSource); + + private readonly LazyTaskState _state; + private readonly short _token; + + /// + /// Gets the task associated with this instance + /// + public Task Task => (Task)(_state?.GetTask(_token) ?? ThrowHelper.ThrowInvalidOperationException>()); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private LazyTaskCompletionSource(LazyTaskState state) + { + _state = state; + _token = state.Version; + } + + /// + /// Create a new instance; this instance should be disposed when it is known to be unwanted + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static LazyTaskCompletionSource Create() + => new LazyTaskCompletionSource(LazyTaskState.Create()); + + /// + /// Create a new instance; this instance will never by recycled + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static LazyTaskCompletionSource CreateConstant(T value) + => new LazyTaskCompletionSource(LazyTaskState.CreateConstant(value)); + + + private static LazyTaskCompletionSource _canceled; + + /// + /// A global LazyTaskCompletionSource that represents a cancelled operation + /// + public static LazyTaskCompletionSource CanceledTask + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => _canceled.IsValid ? _canceled : _canceled = new LazyTaskCompletionSource(LazyTaskState.CreateCanceled()); + } + + /// + /// Set the result of the operation + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool TrySetResult(T result) => _state?.TrySetResult(_token, result) == true; + + /// + /// Set the result of the operation + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void SetResult(T result) + { + if (!TrySetResult(result)) ThrowHelper.ThrowInvalidOperationException(); + } + + /// + /// Set the result of the operation + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool TrySetCanceled(CancellationToken cancellationToken = default) + => _state?.TrySetCanceled(_token, cancellationToken) == true; + + /// + /// Set the result of the operation + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void SetCanceled(CancellationToken cancellationToken = default) + { + if (!TrySetCanceled(cancellationToken)) ThrowHelper.ThrowInvalidOperationException(); + } + + /// + /// Set the result of the operation + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool TrySetException(Exception exception) => _state?.TrySetException(_token, exception) == true; + + /// + /// Set the result of the operation + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void SetException(Exception exception) + { + if (!TrySetException(exception)) ThrowHelper.ThrowInvalidOperationException(); + } + + /// + /// Release all resources associated with this operation + /// + [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; + + /// + /// Indicates whether this is an invalid default instance + /// + public bool IsNull + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => _state == null; + } + } +} diff --git a/src/Admin/ThingsGateway.NewLife.X/PooledAwait/MethodBuilders/FireAndForgetMethodBuilder.cs b/src/Admin/ThingsGateway.NewLife.X/PooledAwait/MethodBuilders/FireAndForgetMethodBuilder.cs new file mode 100644 index 000000000..fee15e0c6 --- /dev/null +++ b/src/Admin/ThingsGateway.NewLife.X/PooledAwait/MethodBuilders/FireAndForgetMethodBuilder.cs @@ -0,0 +1,73 @@ +using PooledAwait.Internal; + +using System.ComponentModel; +using System.Runtime.CompilerServices; + +#pragma warning disable CS1591 + +namespace PooledAwait.MethodBuilders +{ + /// + /// This type is not intended for direct usage + /// + [Browsable(false)] + [EditorBrowsable(EditorBrowsableState.Never)] + public struct FireAndForgetMethodBuilder + { + public override bool Equals(object? obj) => ThrowHelper.ThrowNotSupportedException(); + public override int GetHashCode() => ThrowHelper.ThrowNotSupportedException(); + 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( + ref TAwaiter awaiter, ref TStateMachine stateMachine) + where TAwaiter : INotifyCompletion + where TStateMachine : IAsyncStateMachine + => StateMachineBox.AwaitOnCompleted(ref awaiter, ref stateMachine); + + [Browsable(false)] + [EditorBrowsable(EditorBrowsableState.Never)] + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void AwaitUnsafeOnCompleted( + ref TAwaiter awaiter, ref TStateMachine stateMachine) + where TAwaiter : ICriticalNotifyCompletion + where TStateMachine : IAsyncStateMachine + => StateMachineBox.AwaitUnsafeOnCompleted(ref awaiter, ref stateMachine); + + [Browsable(false)] + [EditorBrowsable(EditorBrowsableState.Never)] + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Start(ref TStateMachine stateMachine) + where TStateMachine : IAsyncStateMachine => stateMachine.MoveNext(); + } +} diff --git a/src/Admin/ThingsGateway.NewLife.X/PooledAwait/MethodBuilders/PooledTaskMethodBuilder.cs b/src/Admin/ThingsGateway.NewLife.X/PooledAwait/MethodBuilders/PooledTaskMethodBuilder.cs new file mode 100644 index 000000000..3567e4058 --- /dev/null +++ b/src/Admin/ThingsGateway.NewLife.X/PooledAwait/MethodBuilders/PooledTaskMethodBuilder.cs @@ -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 +{ + /// + /// This type is not intended for direct usage + /// + [Browsable(false)] + [EditorBrowsable(EditorBrowsableState.Never)] + public struct PooledTaskMethodBuilder + { + public override bool Equals(object? obj) => ThrowHelper.ThrowNotSupportedException(); + public override int GetHashCode() => ThrowHelper.ThrowNotSupportedException(); + public override string ToString() => nameof(PooledTaskMethodBuilder); + + private ValueTaskCompletionSource _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.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.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( + ref TAwaiter awaiter, ref TStateMachine stateMachine) + where TAwaiter : INotifyCompletion + where TStateMachine : IAsyncStateMachine + { + EnsureHasTask(); + StateMachineBox.AwaitOnCompleted(ref awaiter, ref stateMachine); + } + + [Browsable(false)] + [EditorBrowsable(EditorBrowsableState.Never)] + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void AwaitUnsafeOnCompleted( + ref TAwaiter awaiter, ref TStateMachine stateMachine) + where TAwaiter : ICriticalNotifyCompletion + where TStateMachine : IAsyncStateMachine + { + EnsureHasTask(); + StateMachineBox.AwaitUnsafeOnCompleted(ref awaiter, ref stateMachine); + } + + [Browsable(false)] + [EditorBrowsable(EditorBrowsableState.Never)] + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Start(ref TStateMachine stateMachine) + where TStateMachine : IAsyncStateMachine => stateMachine.MoveNext(); + } +} diff --git a/src/Admin/ThingsGateway.NewLife.X/PooledAwait/MethodBuilders/PooledTaskMethodBuilderT.cs b/src/Admin/ThingsGateway.NewLife.X/PooledAwait/MethodBuilders/PooledTaskMethodBuilderT.cs new file mode 100644 index 000000000..889da40d4 --- /dev/null +++ b/src/Admin/ThingsGateway.NewLife.X/PooledAwait/MethodBuilders/PooledTaskMethodBuilderT.cs @@ -0,0 +1,105 @@ +using PooledAwait.Internal; + +using System.ComponentModel; +using System.Runtime.CompilerServices; + +#pragma warning disable CS1591 + +namespace PooledAwait.MethodBuilders +{ + /// + /// This type is not intended for direct usage + /// + [Browsable(false)] + [EditorBrowsable(EditorBrowsableState.Never)] + public struct PooledTaskMethodBuilder + { + public override bool Equals(object? obj) => ThrowHelper.ThrowNotSupportedException(); + public override int GetHashCode() => ThrowHelper.ThrowNotSupportedException(); + public override string ToString() => nameof(PooledTaskMethodBuilder); + + private ValueTaskCompletionSource _source; + private Exception _exception; + private T _result; + + [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(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 Task + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get + { + Task task; + if (!_source.IsNull) task = _source.Task; + else if (_exception is OperationCanceledException) task = TaskUtils.TaskFactory.Canceled; + else if (_exception != null) task = TaskUtils.FromException(_exception); + else task = TaskUtils.TaskFactory.FromResult(_result); + return new PooledTask(task); + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void EnsureHasTask() + { + if (_source.IsNull) _source = ValueTaskCompletionSource.Create(); + } + + [Browsable(false)] + [EditorBrowsable(EditorBrowsableState.Never)] + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void AwaitOnCompleted( + ref TAwaiter awaiter, ref TStateMachine stateMachine) + where TAwaiter : INotifyCompletion + where TStateMachine : IAsyncStateMachine + { + EnsureHasTask(); + StateMachineBox.AwaitOnCompleted(ref awaiter, ref stateMachine); + } + + [Browsable(false)] + [EditorBrowsable(EditorBrowsableState.Never)] + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void AwaitUnsafeOnCompleted( + ref TAwaiter awaiter, ref TStateMachine stateMachine) + where TAwaiter : ICriticalNotifyCompletion + where TStateMachine : IAsyncStateMachine + { + EnsureHasTask(); + StateMachineBox.AwaitUnsafeOnCompleted(ref awaiter, ref stateMachine); + } + + [Browsable(false)] + [EditorBrowsable(EditorBrowsableState.Never)] + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Start(ref TStateMachine stateMachine) + where TStateMachine : IAsyncStateMachine => stateMachine.MoveNext(); + } +} diff --git a/src/Admin/ThingsGateway.NewLife.X/PooledAwait/MethodBuilders/PooledValueTaskMethodBuilder.cs b/src/Admin/ThingsGateway.NewLife.X/PooledAwait/MethodBuilders/PooledValueTaskMethodBuilder.cs new file mode 100644 index 000000000..576e3ffae --- /dev/null +++ b/src/Admin/ThingsGateway.NewLife.X/PooledAwait/MethodBuilders/PooledValueTaskMethodBuilder.cs @@ -0,0 +1,94 @@ +using PooledAwait.Internal; + +using System.ComponentModel; +using System.Runtime.CompilerServices; + +#pragma warning disable CS1591 + +namespace PooledAwait.MethodBuilders +{ + /// + /// This type is not intended for direct usage + /// + [Browsable(false)] + [EditorBrowsable(EditorBrowsableState.Never)] + public struct PooledValueTaskMethodBuilder + { + public override bool Equals(object? obj) => ThrowHelper.ThrowNotSupportedException(); + public override int GetHashCode() => ThrowHelper.ThrowNotSupportedException(); + 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( + ref TAwaiter awaiter, ref TStateMachine stateMachine) + where TAwaiter : INotifyCompletion + where TStateMachine : IAsyncStateMachine + { + EnsureHasTask(); + StateMachineBox.AwaitOnCompleted(ref awaiter, ref stateMachine); + } + + [Browsable(false)] + [EditorBrowsable(EditorBrowsableState.Never)] + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void AwaitUnsafeOnCompleted( + ref TAwaiter awaiter, ref TStateMachine stateMachine) + where TAwaiter : ICriticalNotifyCompletion + where TStateMachine : IAsyncStateMachine + { + EnsureHasTask(); + StateMachineBox.AwaitUnsafeOnCompleted(ref awaiter, ref stateMachine); + } + + [Browsable(false)] + [EditorBrowsable(EditorBrowsableState.Never)] + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Start(ref TStateMachine stateMachine) + where TStateMachine : IAsyncStateMachine => stateMachine.MoveNext(); + } +} diff --git a/src/Admin/ThingsGateway.NewLife.X/PooledAwait/MethodBuilders/PooledValueTaskMethodBuilderT.cs b/src/Admin/ThingsGateway.NewLife.X/PooledAwait/MethodBuilders/PooledValueTaskMethodBuilderT.cs new file mode 100644 index 000000000..c085157ae --- /dev/null +++ b/src/Admin/ThingsGateway.NewLife.X/PooledAwait/MethodBuilders/PooledValueTaskMethodBuilderT.cs @@ -0,0 +1,95 @@ +using PooledAwait.Internal; + +using System.ComponentModel; +using System.Runtime.CompilerServices; + +#pragma warning disable CS1591 + +namespace PooledAwait.MethodBuilders +{ + /// + /// This type is not intended for direct usage + /// + [Browsable(false)] + [EditorBrowsable(EditorBrowsableState.Never)] + public struct PooledValueTaskMethodBuilder + { + public override bool Equals(object? obj) => ThrowHelper.ThrowNotSupportedException(); + public override int GetHashCode() => ThrowHelper.ThrowNotSupportedException(); + 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(T result) + { + if (_source.HasTask) _source.TrySetResult(result); + else _source = new PooledValueTaskSource(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.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( + ref TAwaiter awaiter, ref TStateMachine stateMachine) + where TAwaiter : INotifyCompletion + where TStateMachine : IAsyncStateMachine + { + EnsureHasTask(); + StateMachineBox.AwaitOnCompleted(ref awaiter, ref stateMachine); + } + + [Browsable(false)] + [EditorBrowsable(EditorBrowsableState.Never)] + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void AwaitUnsafeOnCompleted( + ref TAwaiter awaiter, ref TStateMachine stateMachine) + where TAwaiter : ICriticalNotifyCompletion + where TStateMachine : IAsyncStateMachine + { + EnsureHasTask(); + StateMachineBox.AwaitUnsafeOnCompleted(ref awaiter, ref stateMachine); + } + + [Browsable(false)] + [EditorBrowsable(EditorBrowsableState.Never)] + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Start(ref TStateMachine stateMachine) + where TStateMachine : IAsyncStateMachine => stateMachine.MoveNext(); + } +} diff --git a/src/Admin/ThingsGateway.NewLife.X/PooledAwait/Pool.cs b/src/Admin/ThingsGateway.NewLife.X/PooledAwait/Pool.cs new file mode 100644 index 000000000..02bee303c --- /dev/null +++ b/src/Admin/ThingsGateway.NewLife.X/PooledAwait/Pool.cs @@ -0,0 +1,73 @@ +using PooledAwait.Internal; + +using System.Runtime.CompilerServices; + +namespace PooledAwait +{ + /// + /// Utility methods for boxing value types efficiently, in particular for + /// avoid boxes and capture contexts in callbacks + /// + public static class Pool + { + /// + /// Gets an instance from the pool if possible + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static T? TryRent() where T : class + => Pool.TryGet(); + + /// + /// Puts an instance back into the pool + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void Return(T value) where T : class + { + if (value is IResettable reset) reset.Reset(); + Pool.TryPut(value); + } + + /// + /// Wraps a value-type into a boxed instance, using an object pool; + /// consider using value-tuples in particular + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static object Box(in T value) where T : struct + => ItemBox.Create(in value); + + /// + /// Unwraps a value-type from a boxed instance and recycles + /// the instance, which should not be touched again + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static T UnboxAndReturn(object obj) where T : struct + => ItemBox.UnboxAndRecycle(obj); + + internal sealed class ItemBox where T : struct + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private ItemBox() => Counters.ItemBoxAllocated.Increment(); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static ItemBox Create(in T value) + { + var box = Pool>.TryGet() ?? new ItemBox(); + box._value = value; + return box; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static T UnboxAndRecycle(object obj) + { + var box = (ItemBox)obj; + var value = box._value; + box._value = default; + Pool>.TryPut(box); + return value; + } +#pragma warning disable IDE0044 // make field readonly? no, IDE, you're wrong + private T _value; +#pragma warning restore IDE0044 + } + } +} diff --git a/src/Admin/ThingsGateway.NewLife.X/PooledAwait/PoolSizeAttribute.cs b/src/Admin/ThingsGateway.NewLife.X/PooledAwait/PoolSizeAttribute.cs new file mode 100644 index 000000000..dc94f9b5d --- /dev/null +++ b/src/Admin/ThingsGateway.NewLife.X/PooledAwait/PoolSizeAttribute.cs @@ -0,0 +1,21 @@ +#if !NETSTANDARD1_3 +namespace PooledAwait +{ + /// + /// Controls the number of elements to store in the pool + /// + [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct, AllowMultiple = false, Inherited = true)] + public sealed class PoolSizeAttribute : Attribute + { + /// + /// The number of elements to store in the pool + /// + public int Size { get; } + + /// + /// Create a new PoolSizeAttribute instance + /// + public PoolSizeAttribute(int size) => Size = size; + } +} +#endif diff --git a/src/Admin/ThingsGateway.NewLife.X/PooledAwait/PoolT.cs b/src/Admin/ThingsGateway.NewLife.X/PooledAwait/PoolT.cs new file mode 100644 index 000000000..0a766e642 --- /dev/null +++ b/src/Admin/ThingsGateway.NewLife.X/PooledAwait/PoolT.cs @@ -0,0 +1,47 @@ +using System.Runtime.CompilerServices; + +using ThingsGateway.NewLife.Collections; + +namespace PooledAwait +{ + /// + /// A general-purpose pool of object references; it is the caller's responsibility + /// to ensure that overlapped usage does not occur + /// + internal static class Pool where T : class + { + private static ObjectPoolLock pool = new(); + + [ThreadStatic] + private static T? ts_local; + + /// + /// Gets an instance from the pool if possible + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static T? TryGet() + { + var tmp = ts_local; + ts_local = null; + return tmp ?? pool.Get(); + } + + /// + /// Puts an instance back into the pool + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void TryPut(T value) + { + if (value != null) + { + if (ts_local == null) + { + ts_local = value; + return; + } + pool.Return(value); + } + } + } + +} diff --git a/src/Admin/ThingsGateway.NewLife.X/PooledAwait/PooledAwait.csproj b/src/Admin/ThingsGateway.NewLife.X/PooledAwait/PooledAwait.csproj new file mode 100644 index 000000000..57ceeb080 --- /dev/null +++ b/src/Admin/ThingsGateway.NewLife.X/PooledAwait/PooledAwait.csproj @@ -0,0 +1,29 @@ + + + + + false + + + + $(DefineConstants);PLAT_THREADPOOLWORKITEM;PLAT_MRVTSC + + + $(DefineConstants);PLAT_THREADPOOLWORKITEM;PLAT_MRVTSC + + + + $(DefineConstants);PLAT_MRVTSC + true + + + + $(DefineConstants);PLAT_MRVTSC + true + + + + + + + \ No newline at end of file diff --git a/src/Admin/ThingsGateway.NewLife.X/PooledAwait/PooledTask.cs b/src/Admin/ThingsGateway.NewLife.X/PooledAwait/PooledTask.cs new file mode 100644 index 000000000..3959ae4bb --- /dev/null +++ b/src/Admin/ThingsGateway.NewLife.X/PooledAwait/PooledTask.cs @@ -0,0 +1,60 @@ +using PooledAwait.Internal; + +using System.Runtime.CompilerServices; + +namespace PooledAwait +{ + /// + /// A Task, but with a custom builder + /// + [AsyncMethodBuilder(typeof(MethodBuilders.PooledTaskMethodBuilder))] + public readonly struct PooledTask + { + /// + public override bool Equals(object? obj) => obj is PooledTask pt && _task == pt._task; + /// + public override int GetHashCode() => _task == null ? 0 : _task.GetHashCode(); + /// + public override string ToString() => nameof(PooledTask); + + private readonly Task _task; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal PooledTask(Task task) => _task = task; + + /// + /// Gets the instance as a task + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Task AsTask() => _task ?? TaskUtils.CompletedTask; + + /// + /// Gets the instance as a task + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + + public static implicit operator Task(in PooledTask task) => task.AsTask(); + + /// + /// Gets the awaiter for the task + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public TaskAwaiter GetAwaiter() => AsTask().GetAwaiter(); + + /// + /// Gets the configured awaiter for the task + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public ConfiguredTaskAwaitable ConfigureAwait(bool continueOnCapturedContext) + => AsTask().ConfigureAwait(continueOnCapturedContext); + + /// + /// Indicates whether this is an invalid default instance + /// + public bool IsNull + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => _task == null; + } + } +} diff --git a/src/Admin/ThingsGateway.NewLife.X/PooledAwait/PooledTaskT.cs b/src/Admin/ThingsGateway.NewLife.X/PooledAwait/PooledTaskT.cs new file mode 100644 index 000000000..fdc503454 --- /dev/null +++ b/src/Admin/ThingsGateway.NewLife.X/PooledAwait/PooledTaskT.cs @@ -0,0 +1,60 @@ +using PooledAwait.Internal; + +using System.Runtime.CompilerServices; + +namespace PooledAwait +{ + /// + /// A Task, but with a custom builder + /// + [AsyncMethodBuilder(typeof(MethodBuilders.PooledTaskMethodBuilder<>))] + public readonly struct PooledTask + { + /// + public override bool Equals(object? obj) => obj is PooledTask pt && _task == pt._task; + /// + public override int GetHashCode() => _task == null ? 0 : _task.GetHashCode(); + /// + public override string ToString() => nameof(PooledTask); + + private readonly Task? _task; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal PooledTask(Task task) => _task = task; + + /// + /// Gets the instance as a task + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Task AsTask() => _task ?? ThrowHelper.ThrowInvalidOperationException>(); + + /// + /// Gets the instance as a task + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + + public static implicit operator Task(in PooledTask task) => task.AsTask(); + + /// + /// Gets the awaiter for the task + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public TaskAwaiter GetAwaiter() => AsTask().GetAwaiter(); + + /// + /// Gets the configured awaiter for the task + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public ConfiguredTaskAwaitable ConfigureAwait(bool continueOnCapturedContext) + => AsTask().ConfigureAwait(continueOnCapturedContext); + + /// + /// Indicates whether this is an invalid default instance + /// + public bool IsNull + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => _task == null; + } + } +} diff --git a/src/Admin/ThingsGateway.NewLife.X/PooledAwait/PooledValueTask.cs b/src/Admin/ThingsGateway.NewLife.X/PooledAwait/PooledValueTask.cs new file mode 100644 index 000000000..3c4d2d0c5 --- /dev/null +++ b/src/Admin/ThingsGateway.NewLife.X/PooledAwait/PooledValueTask.cs @@ -0,0 +1,71 @@ +using System.Runtime.CompilerServices; +using System.Threading.Tasks.Sources; + +namespace PooledAwait +{ + /// + /// A ValueTask with a custom source and builder + /// + [AsyncMethodBuilder(typeof(MethodBuilders.PooledValueTaskMethodBuilder))] + public readonly struct PooledValueTask + { + /// + public override bool Equals(object? obj) => obj is PooledValueTask pvt && _source == pvt._source && _token == pvt._token; + /// + public override int GetHashCode() => (_source == null ? 0 : _source.GetHashCode()) ^ _token; + /// + 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; + } + + /// + /// Gets the instance as a value-task + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public ValueTask AsValueTask() => _source == null ? default : new ValueTask(_source, _token); + + /// + /// Gets the instance as a value-task + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static implicit operator ValueTask(in PooledValueTask task) => task.AsValueTask(); + + /// + /// Gets the awaiter for the task + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] +#pragma warning disable CA2012 // 正确使用 ValueTask + public ValueTaskAwaiter GetAwaiter() => AsValueTask().GetAwaiter(); +#pragma warning restore CA2012 // 正确使用 ValueTask + + /// + /// Gets the configured awaiter for the task + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public ConfiguredValueTaskAwaitable ConfigureAwait(bool continueOnCapturedContext) + => AsValueTask().ConfigureAwait(continueOnCapturedContext); + + /// + /// Rents a task-source that will be recycled when the task is awaited + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static PooledValueTaskSource CreateSource() => PooledValueTaskSource.Create(); + + /// + /// Indicates whether this is an invalid default instance + /// + public bool IsNull + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => _source == null; + } + } +} diff --git a/src/Admin/ThingsGateway.NewLife.X/PooledAwait/PooledValueTaskSource.cs b/src/Admin/ThingsGateway.NewLife.X/PooledAwait/PooledValueTaskSource.cs new file mode 100644 index 000000000..2e3cebf91 --- /dev/null +++ b/src/Admin/ThingsGateway.NewLife.X/PooledAwait/PooledValueTaskSource.cs @@ -0,0 +1,126 @@ +using PooledAwait.Internal; + +using System.Runtime.CompilerServices; + +namespace PooledAwait +{ + /// + /// A task-source that automatically recycles when the task is awaited + /// + public readonly struct PooledValueTaskSource + { + /// + public override bool Equals(object? obj) => obj is PooledValueTaskSource pvt && _source == pvt._source && _token == pvt._token; + /// + public override int GetHashCode() => (_source == null ? 0 : _source.GetHashCode()) ^ _token; + /// + public override string ToString() => nameof(PooledValueTaskSource); + + /// + /// Gets the task that corresponds to this instance; it can only be awaited once + /// + public ValueTask Task + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => _source == null ? default : new ValueTask(_source, _token); + } + + /// + /// Indicates whether this instance is well-defined against a value task instance + /// + public bool HasTask + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => _source != null; + } + + internal PooledValueTask PooledTask + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => new PooledValueTask(_source, _token); + } + + /// + /// Rents a task-source that will be recycled when the task is awaited + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static PooledValueTaskSource Create() + { + var source = PooledState.Create(out var token); + return new PooledValueTaskSource(source, token); + } + + private readonly PooledState _source; + private readonly short _token; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal PooledValueTaskSource(PooledState source, short token) + { + _source = source; + _token = token; + } + + /// + /// Test whether the source is valid + /// + public bool IsValid + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => _source?.IsValid(_token) == true; + } + + /// + /// Set the result of the operation + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool TrySetResult() => _source?.TrySetResult(default, _token) == true; + + /// + /// Set the result of the operation + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void SetResult() + { + if (!TrySetResult()) ThrowHelper.ThrowInvalidOperationException(); + } + + /// + /// Set the result of the operation + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool TrySetException(Exception error) => _source?.TrySetException(error, _token) == true; + + /// + /// Set the result of the operation + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void SetException(Exception exception) + { + if (!TrySetException(exception)) ThrowHelper.ThrowInvalidOperationException(); + } + + /// + /// Set the result of the operation + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool TrySetCanceled() => _source?.TrySetCanceled(_token) == true; + + /// + /// Set the result of the operation + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void SetCanceled() + { + if (!TrySetCanceled()) ThrowHelper.ThrowInvalidOperationException(); + } + + /// + /// Indicates whether this is an invalid default instance + /// + public bool IsNull + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => _source == null; + } + } +} diff --git a/src/Admin/ThingsGateway.NewLife.X/PooledAwait/PooledValueTaskSourceT.cs b/src/Admin/ThingsGateway.NewLife.X/PooledAwait/PooledValueTaskSourceT.cs new file mode 100644 index 000000000..e9c0d5091 --- /dev/null +++ b/src/Admin/ThingsGateway.NewLife.X/PooledAwait/PooledValueTaskSourceT.cs @@ -0,0 +1,141 @@ +using PooledAwait.Internal; + +using System.Runtime.CompilerServices; + +namespace PooledAwait +{ + /// + /// A task-source that automatically recycles when the task is awaited + /// + public readonly struct PooledValueTaskSource + { + /// + public override bool Equals(object? obj) => obj is PooledValueTaskSource pvt && _token == pvt._token && + (_source != null ? _source == pvt._source : (pvt._source == null && EqualityComparer.Default.Equals(_value, pvt._value))); + /// + public override int GetHashCode() => (_source == null ? EqualityComparer.Default.GetHashCode(_value!) : _source.GetHashCode()) ^ _token; + /// + public override string ToString() => nameof(PooledValueTaskSource); + + /// + /// Gets the task that corresponds to this instance; it can only be awaited once + /// + public ValueTask Task + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => _source == null ? new ValueTask(_value) : new ValueTask(_source, _token); + } + + /// + /// Indicates whether this instance is well-defined against a value task instance + /// + public bool HasTask + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => _source != null; + } + + internal PooledValueTask PooledTask + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => _source == null ? new PooledValueTask(_value) : new PooledValueTask(_source, _token); + } + + /// + /// Rents a task-source that will be recycled when the task is awaited + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static PooledValueTaskSource Create() + { + var source = PooledState.Create(out var token); + return new PooledValueTaskSource(source, token); + } + + private readonly PooledState? _source; + private readonly short _token; + + private readonly T _value; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal PooledValueTaskSource(PooledState source, short token) + { + _source = source; + _token = token; + _value = default!; + } + + /// + /// Create a new PooledValueTaskSource that will yield a constant value without ever renting/recycling any background state + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public PooledValueTaskSource(T value) + { + _source = null; + _token = default; + _value = value!; + } + + /// + /// Test whether the source is valid + /// + public bool IsValid + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => _source?.IsValid(_token) == true; + } + + /// + /// Set the result of the operation + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool TrySetResult(T result) => _source?.TrySetResult(result, _token) == true; + + /// + /// Set the result of the operation + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void SetResult(T result) + { + if (!TrySetResult(result)) ThrowHelper.ThrowInvalidOperationException(); + } + + /// + /// Set the result of the operation + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool TrySetException(Exception error) => _source?.TrySetException(error, _token) == true; + + /// + /// Set the result of the operation + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void SetException(Exception exception) + { + if (!TrySetException(exception)) ThrowHelper.ThrowInvalidOperationException(); + } + + /// + /// Set the result of the operation + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool TrySetCanceled() => _source?.TrySetCanceled(_token) == true; + + /// + /// Set the result of the operation + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void SetCanceled() + { + if (!TrySetCanceled()) ThrowHelper.ThrowInvalidOperationException(); + } + + /// + /// Indicates whether this is an invalid default instance + /// + public bool IsNull + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => _source == null; + } + } +} diff --git a/src/Admin/ThingsGateway.NewLife.X/PooledAwait/PooledValueTaskT.cs b/src/Admin/ThingsGateway.NewLife.X/PooledAwait/PooledValueTaskT.cs new file mode 100644 index 000000000..457ec5f4c --- /dev/null +++ b/src/Admin/ThingsGateway.NewLife.X/PooledAwait/PooledValueTaskT.cs @@ -0,0 +1,85 @@ +using PooledAwait.Internal; + +using System.Runtime.CompilerServices; + +namespace PooledAwait +{ + /// + /// A ValueTask with a custom source and builder + /// + [AsyncMethodBuilder(typeof(MethodBuilders.PooledValueTaskMethodBuilder<>))] + public readonly struct PooledValueTask + { + /// + public override bool Equals(object? obj) => obj is PooledValueTask pvt && _source == pvt._source && _token == pvt._token; + /// + public override int GetHashCode() => (_source == null ? 0 : _source.GetHashCode()) ^ _token; + /// + public override string ToString() => nameof(PooledValueTask); + + private readonly PooledState? _source; + private readonly short _token; + private readonly T _result; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal PooledValueTask(PooledState source, short token) + { + _source = source; + _token = token; + _result = default!; + } + + /// + /// Creates a value-task with a fixed value + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public PooledValueTask(T result) + { + _source = default; + _token = default; + _result = result; + } + + /// + /// Gets the instance as a value-task + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public ValueTask AsValueTask() => _source == null ? new ValueTask(_result) : new ValueTask(_source, _token); + + /// + /// Gets the instance as a value-task + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static implicit operator ValueTask(in PooledValueTask task) => task.AsValueTask(); + + /// + /// Gets the awaiter for the task + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] +#pragma warning disable CA2012 // 正确使用 ValueTask + public ValueTaskAwaiter GetAwaiter() => AsValueTask().GetAwaiter(); +#pragma warning restore CA2012 // 正确使用 ValueTask + + /// + /// Gets the configured awaiter for the task + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public ConfiguredValueTaskAwaitable ConfigureAwait(bool continueOnCapturedContext) + => AsValueTask().ConfigureAwait(continueOnCapturedContext); + + /// + /// Rents a task-source that will be recycled when the task is awaited + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static PooledValueTaskSource CreateSource() => PooledValueTaskSource.Create(); + + /// + /// Indicates whether this is an invalid default instance + /// + public bool IsNull + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => _source == null; + } + } +} diff --git a/src/Admin/ThingsGateway.NewLife.X/PooledAwait/Properties/AssemblyInfo.cs b/src/Admin/ThingsGateway.NewLife.X/PooledAwait/Properties/AssemblyInfo.cs new file mode 100644 index 000000000..80ed85374 --- /dev/null +++ b/src/Admin/ThingsGateway.NewLife.X/PooledAwait/Properties/AssemblyInfo.cs @@ -0,0 +1,5 @@ +using System.Runtime.CompilerServices; + +[assembly: InternalsVisibleTo("Benchmark, PublicKey=0024000004800000940000000602000000240000525341310004000001000100d136a87a0c11ded39332f7ead1f2ce53256a42a350ee847df1dc520bc781210a5f1fe73b3f91a4001fdcfa35340a212e70443de46e6928510f57a55f4ab9b95257f5067d4de4be8cdad460781866b20a6ca20f66302df61b52e55e16c080cad95aae8b94ffdd54718b9963d0240b0d0d5afe655b05c91771f7dc8a314387d3c3")] + +[assembly: InternalsVisibleTo("PooledAwait.Test, PublicKey=0024000004800000940000000602000000240000525341310004000001000100d136a87a0c11ded39332f7ead1f2ce53256a42a350ee847df1dc520bc781210a5f1fe73b3f91a4001fdcfa35340a212e70443de46e6928510f57a55f4ab9b95257f5067d4de4be8cdad460781866b20a6ca20f66302df61b52e55e16c080cad95aae8b94ffdd54718b9963d0240b0d0d5afe655b05c91771f7dc8a314387d3c3")] \ No newline at end of file diff --git a/src/Admin/ThingsGateway.NewLife.X/PooledAwait/ValueTaskCompletionSourceT.cs b/src/Admin/ThingsGateway.NewLife.X/PooledAwait/ValueTaskCompletionSourceT.cs new file mode 100644 index 000000000..5bb49cde8 --- /dev/null +++ b/src/Admin/ThingsGateway.NewLife.X/PooledAwait/ValueTaskCompletionSourceT.cs @@ -0,0 +1,218 @@ +using System.Runtime.CompilerServices; + +using PooledAwait.Internal; + +#if !NETSTANDARD1_3 +using System.Reflection; +#endif + +namespace PooledAwait +{ + /// + /// Lightweight implementation of TaskCompletionSource + /// + /// When possible, this will bypass TaskCompletionSource completely + public readonly struct ValueTaskCompletionSource + { + /// + public override bool Equals(object? obj) => obj is ValueTaskCompletionSource other && _state == other._state; + /// + public override int GetHashCode() => _state == null ? 0 : _state.GetHashCode(); + /// + public override string ToString() => "ValueTaskCompletionSource"; + +#if !NETSTANDARD1_3 + private static readonly Func, Exception, bool>? s_TrySetException = TryCreate(nameof(TrySetException)); + private static readonly Func, T, bool>? s_TrySetResult = TryCreate(nameof(TrySetResult)); + private static readonly Func, CancellationToken, bool>? s_TrySetCanceled = TryCreate(nameof(TrySetCanceled)); + private static readonly bool s_Optimized = ValidateOptimized(); +#endif + private readonly object _state; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private ValueTaskCompletionSource(object state) => _state = state; + + /// + /// Gets the instance as a task + /// + public Task Task + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => _state as Task ?? ((TaskCompletionSource)_state).Task; + } + + /// + /// Indicates whether this is an invalid default instance + /// + public bool IsNull + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => _state == null; + } + + internal bool IsOptimized => _state is Task; + + /// + /// Create an instance pointing to a new task + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static ValueTaskCompletionSource Create() => +#if !NETSTANDARD1_3 + s_Optimized ? CreateOptimized() : +#endif + CreateFallback(); + +#if !NETSTANDARD1_3 + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static ValueTaskCompletionSource CreateOptimized() + { + Counters.TaskAllocated.Increment(); + return new ValueTaskCompletionSource(new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously)); + } +#endif + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static ValueTaskCompletionSource CreateFallback() + { + Counters.TaskAllocated.Increment(); + return new ValueTaskCompletionSource(new TaskCompletionSource()); + } + + /// + /// Set the outcome of the operation + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool TrySetException(Exception exception) + { +#if !NETSTANDARD1_3 + if (_state is Task task) + { + var result = s_TrySetException!(task, exception); + if (!result && !task.IsCompleted) SpinUntilCompleted(task); + return result; + } +#endif + return _state != null && ((TaskCompletionSource)_state).TrySetException(exception); + } + + /// + /// Set the result of the operation + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void SetException(Exception exception) + { + if (!TrySetException(exception)) ThrowHelper.ThrowInvalidOperationException(); + } + + /// + /// Set the outcome of the operation + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool TrySetCanceled(CancellationToken cancellationToken = default) + { +#if !NETSTANDARD1_3 + if (_state is Task task) + { + var result = s_TrySetCanceled!(task, cancellationToken); + if (!result && !task.IsCompleted) SpinUntilCompleted(task); + return result; + } +#endif + return _state != null && ((TaskCompletionSource)_state).TrySetCanceled( +#if !NET45 + cancellationToken +#endif + ); + } + + /// + /// Set the result of the operation + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void SetCanceled(CancellationToken cancellationToken = default) + { + if (!TrySetCanceled(cancellationToken)) ThrowHelper.ThrowInvalidOperationException(); + } + + /// + /// Set the outcome of the operation + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool TrySetResult(T value) + { +#if !NETSTANDARD1_3 + if (_state is Task task) + { + var result = s_TrySetResult!(task, value); + if (!result && !task.IsCompleted) SpinUntilCompleted(task); + return result; + } +#endif + return _state != null && ((TaskCompletionSource)_state).TrySetResult(value); + } + + /// + /// Set the result of the operation + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void SetResult(T value) + { + if (!TrySetResult(value)) ThrowHelper.ThrowInvalidOperationException(); + } + +#if !NETSTANDARD1_3 + [MethodImpl(MethodImplOptions.NoInlining)] + private static Func, TArg, bool>? TryCreate(string methodName) + { + try + { + return (Func, TArg, bool>)Delegate.CreateDelegate( + typeof(Func, TArg, bool>), + typeof(Task).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 task) + { + // Spin wait until the completion is finalized by another thread. + var sw = new SpinWait(); + while (!task.IsCompleted) + sw.SpinOnce(); + } +#endif + } +} diff --git a/src/Admin/ThingsGateway.NewLife.X/ThingsGateway.NewLife.X.csproj b/src/Admin/ThingsGateway.NewLife.X/ThingsGateway.NewLife.X.csproj index 4acfd5460..531686e68 100644 --- a/src/Admin/ThingsGateway.NewLife.X/ThingsGateway.NewLife.X.csproj +++ b/src/Admin/ThingsGateway.NewLife.X/ThingsGateway.NewLife.X.csproj @@ -24,7 +24,7 @@ - + __WIN__ @@ -54,7 +54,26 @@ + + $(DefineConstants);PLAT_THREADPOOLWORKITEM;PLAT_MRVTSC + + + $(DefineConstants);PLAT_THREADPOOLWORKITEM;PLAT_MRVTSC + + + $(DefineConstants);PLAT_MRVTSC + true + + + + $(DefineConstants);PLAT_MRVTSC + true + + + + +