mirror of
https://gitee.com/ThingsGateway/ThingsGateway.git
synced 2025-10-20 10:50:48 +08:00
pref: 异步状态机优化
This commit is contained in:
@@ -4,7 +4,6 @@
|
||||
|
||||
#nullable disable
|
||||
|
||||
using System.Collections;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace System.Collections.Concurrent
|
||||
|
@@ -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;
|
||||
|
||||
|
@@ -31,8 +31,8 @@ public class ObjectPool<T> : DisposeBase, IPool<T> where T : notnull
|
||||
/// <summary>最小个数。默认1</summary>
|
||||
public Int32 Min { get; set; } = 1;
|
||||
|
||||
/// <summary>空闲清理时间。最小个数之上的资源超过空闲时间时被清理,默认10s</summary>
|
||||
public Int32 IdleTime { get; set; } = 10;
|
||||
/// <summary>空闲清理时间。最小个数之上的资源超过空闲时间时被清理,默认60s</summary>
|
||||
public Int32 IdleTime { get; set; } = 60;
|
||||
|
||||
/// <summary>完全空闲清理时间。最小个数之下的资源超过空闲时间时被清理,默认0s永不清理</summary>
|
||||
public Int32 AllIdleTime { get; set; } = 0;
|
||||
@@ -126,9 +126,7 @@ public class ObjectPool<T> : DisposeBase, IPool<T> where T : notnull
|
||||
if (Max > 0 && count >= Max)
|
||||
{
|
||||
var msg = $"申请失败,已有 {count:n0} 达到或超过最大值 {Max:n0}";
|
||||
|
||||
WriteLog("Acquire Max " + msg);
|
||||
|
||||
throw new Exception(Name + " " + msg);
|
||||
}
|
||||
|
||||
@@ -268,7 +266,7 @@ public class ObjectPool<T> : DisposeBase, IPool<T> 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<T> : DisposeBase, IPool<T> 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)
|
||||
|
262
src/Admin/ThingsGateway.NewLife.X/Collections/ObjectPoolLock.cs
Normal file
262
src/Admin/ThingsGateway.NewLife.X/Collections/ObjectPoolLock.cs
Normal file
@@ -0,0 +1,262 @@
|
||||
using ThingsGateway.NewLife.Log;
|
||||
using ThingsGateway.NewLife.Reflection;
|
||||
|
||||
namespace ThingsGateway.NewLife.Collections;
|
||||
|
||||
/// <summary>资源池。支持空闲释放,主要用于数据库连接池和网络连接池</summary>
|
||||
/// <remarks>
|
||||
/// 文档 https://newlifex.com/core/object_pool
|
||||
/// </remarks>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
public class ObjectPoolLock<T> : DisposeBase, IPool<T> where T : class
|
||||
{
|
||||
#region 属性
|
||||
/// <summary>名称</summary>
|
||||
public String Name { get; set; }
|
||||
|
||||
private Int32 _FreeCount;
|
||||
/// <summary>空闲个数</summary>
|
||||
public Int32 FreeCount => _FreeCount;
|
||||
|
||||
private Int32 _BusyCount;
|
||||
/// <summary>繁忙个数</summary>
|
||||
public Int32 BusyCount => _BusyCount;
|
||||
|
||||
/// <summary>最大个数。默认0,0表示无上限</summary>
|
||||
public Int32 Max { get; set; } = 0;
|
||||
|
||||
/// <summary>最小个数。默认1</summary>
|
||||
public Int32 Min { get; set; } = 1;
|
||||
|
||||
private readonly object _syncRoot = new();
|
||||
|
||||
/// <summary>基础空闲集合。只保存最小个数,最热部分</summary>
|
||||
private readonly Stack<T> _free = new();
|
||||
|
||||
/// <summary>扩展空闲集合。保存最小个数以外部分</summary>
|
||||
private readonly Queue<T> _free2 = new();
|
||||
|
||||
/// <summary>借出去的放在这</summary>
|
||||
private readonly HashSet<T> _busy = new();
|
||||
|
||||
//private readonly Object SyncRoot = new();
|
||||
#endregion
|
||||
|
||||
#region 构造
|
||||
/// <summary>实例化一个资源池</summary>
|
||||
public ObjectPoolLock()
|
||||
{
|
||||
var str = GetType().Name;
|
||||
if (str.Contains('`')) str = str.Substring(null, "`");
|
||||
if (str != "Pool")
|
||||
Name = str;
|
||||
else
|
||||
Name = $"Pool<{typeof(T).Name}>";
|
||||
|
||||
}
|
||||
~ObjectPoolLock()
|
||||
{
|
||||
this.TryDispose();
|
||||
}
|
||||
/// <summary>销毁</summary>
|
||||
/// <param name="disposing"></param>
|
||||
protected override void Dispose(Boolean disposing)
|
||||
{
|
||||
base.Dispose(disposing);
|
||||
|
||||
WriteLog($"Dispose {typeof(T).FullName} FreeCount={FreeCount:n0} BusyCount={BusyCount:n0}");
|
||||
|
||||
Clear();
|
||||
}
|
||||
|
||||
private volatile Boolean _inited;
|
||||
private void Init()
|
||||
{
|
||||
if (_inited) return;
|
||||
|
||||
lock (lockThis)
|
||||
{
|
||||
if (_inited) return;
|
||||
_inited = true;
|
||||
|
||||
WriteLog($"Init {typeof(T).FullName} Min={Min} Max={Max}");
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region 主方法
|
||||
/// <summary>借出</summary>
|
||||
/// <returns></returns>
|
||||
public virtual T Get()
|
||||
{
|
||||
T? pi = null;
|
||||
do
|
||||
{
|
||||
lock (_syncRoot)
|
||||
{
|
||||
if (_free.Count > 0)
|
||||
{
|
||||
pi = _free.Pop();
|
||||
_FreeCount--;
|
||||
}
|
||||
else if (_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;
|
||||
}
|
||||
|
||||
/// <summary>借出时是否可用</summary>
|
||||
/// <param name="value"></param>
|
||||
/// <returns></returns>
|
||||
protected virtual Boolean OnGet(T value) => true;
|
||||
|
||||
/// <summary>申请资源包装项,Dispose时自动归还到池中</summary>
|
||||
/// <returns></returns>
|
||||
public PoolItem<T> GetItem() => new(this, Get());
|
||||
|
||||
/// <summary>归还</summary>
|
||||
/// <param name="value"></param>
|
||||
public virtual Boolean Return(T value)
|
||||
{
|
||||
if (value == null) return false;
|
||||
lock (_syncRoot)
|
||||
{
|
||||
// 从繁忙队列找到并移除缓存项
|
||||
if (!_busy.Remove(value))
|
||||
{
|
||||
#if DEBUG
|
||||
WriteLog("Return Error");
|
||||
#endif
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
_BusyCount--;
|
||||
}
|
||||
|
||||
// 是否可用
|
||||
if (!OnReturn(value))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (value is DisposeBase db && db.Disposed)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
lock (_syncRoot)
|
||||
{
|
||||
if (_FreeCount < Min)
|
||||
_free.Push(value);
|
||||
else
|
||||
_free2.Enqueue(value);
|
||||
_FreeCount++;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>归还时是否可用</summary>
|
||||
/// <param name="value"></param>
|
||||
/// <returns></returns>
|
||||
protected virtual Boolean OnReturn(T value) => true;
|
||||
|
||||
/// <summary>清空已有对象</summary>
|
||||
public virtual Int32 Clear()
|
||||
{
|
||||
var count = _FreeCount + _BusyCount;
|
||||
|
||||
//_busy.Clear();
|
||||
//_BusyCount = 0;
|
||||
|
||||
//_free.Clear();
|
||||
//while (_free2.TryDequeue(out var rs)) ;
|
||||
//_FreeCount = 0;
|
||||
|
||||
lock (_syncRoot)
|
||||
{
|
||||
count = _FreeCount + _BusyCount;
|
||||
|
||||
while (_free.Count > 0)
|
||||
{
|
||||
var pi = _free.Pop();
|
||||
OnDispose(pi);
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
/// <summary>销毁</summary>
|
||||
/// <param name="value"></param>
|
||||
protected virtual void OnDispose(T? value) => value.TryDispose();
|
||||
#endregion
|
||||
|
||||
#region 重载
|
||||
/// <summary>创建实例</summary>
|
||||
/// <returns></returns>
|
||||
protected virtual T? OnCreate() => (T?)typeof(T).CreateInstance();
|
||||
#endregion
|
||||
protected object lockThis = new();
|
||||
|
||||
#region 日志
|
||||
/// <summary>日志</summary>
|
||||
public ILog Log { get; set; } = Logger.Null;
|
||||
|
||||
/// <summary>写日志</summary>
|
||||
/// <param name="format"></param>
|
||||
/// <param name="args"></param>
|
||||
public void WriteLog(String format, params Object?[] args)
|
||||
{
|
||||
if (Log?.Enable != true) return;
|
||||
|
||||
Log.Info(Name + "." + format, args);
|
||||
}
|
||||
#endregion
|
||||
}
|
@@ -0,0 +1,48 @@
|
||||
namespace ThingsGateway.NewLife.Collections;
|
||||
|
||||
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
|
||||
internal struct ValueStopwatch
|
||||
{
|
||||
#if !NET7_0_OR_GREATER
|
||||
private static readonly double TimestampToTicks = TimeSpan.TicksPerSecond / (double)Stopwatch.Frequency;
|
||||
#endif
|
||||
|
||||
private readonly long _startTimestamp;
|
||||
|
||||
public bool IsActive => _startTimestamp != 0;
|
||||
|
||||
private ValueStopwatch(long startTimestamp)
|
||||
{
|
||||
_startTimestamp = startTimestamp;
|
||||
}
|
||||
|
||||
public static ValueStopwatch StartNew() => new ValueStopwatch(Stopwatch.GetTimestamp());
|
||||
|
||||
public static TimeSpan GetElapsedTime(long startingTimestamp, long endingTimestamp)
|
||||
{
|
||||
#if !NET7_0_OR_GREATER
|
||||
var timestampDelta = endingTimestamp - startingTimestamp;
|
||||
var ticks = (long)(TimestampToTicks * timestampDelta);
|
||||
return new TimeSpan(ticks);
|
||||
#else
|
||||
return Stopwatch.GetElapsedTime(startingTimestamp, endingTimestamp);
|
||||
#endif
|
||||
}
|
||||
|
||||
public TimeSpan GetElapsedTime()
|
||||
{
|
||||
// Start timestamp can't be zero in an initialized ValueStopwatch. It would have to be literally the first thing executed when the machine boots to be 0.
|
||||
// So it being 0 is a clear indication of default(ValueStopwatch)
|
||||
if (!IsActive)
|
||||
{
|
||||
throw new InvalidOperationException("An uninitialized, or 'default', ValueStopwatch cannot be used to get elapsed time.");
|
||||
}
|
||||
|
||||
var end = Stopwatch.GetTimestamp();
|
||||
|
||||
return GetElapsedTime(_startTimestamp, end);
|
||||
}
|
||||
}
|
@@ -62,7 +62,7 @@ public class ExpiringDictionary<TKey, TValue> : IDisposable
|
||||
this.comparer = comparer;
|
||||
_dict = new NonBlockingDictionary<TKey, CacheItem>(comparer);
|
||||
|
||||
_cleanupTimer = new TimerX(TimerClear, null, 10000, 10000) { Async = true };
|
||||
_cleanupTimer = new TimerX(TimerClear, null, 60000, 60000) { Async = true };
|
||||
}
|
||||
|
||||
public bool TryAdd(TKey key, TValue value)
|
||||
|
@@ -90,15 +90,50 @@ public sealed class WaitLock : IDisposable
|
||||
/// </summary>
|
||||
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
|
||||
/// <summary>Performs the asynchronous wait.</summary>
|
||||
/// <param name="asyncWaiter">The asynchronous waiter.</param>
|
||||
/// <param name="millisecondsTimeout">The timeout.</param>
|
||||
/// <param name="cancellationToken">The cancellation token.</param>
|
||||
/// <returns>The task to return to the caller.</returns>
|
||||
private Task<bool> WaitUntilCountOrTimeoutAsync(Task<bool> asyncWaiter, int millisecondsTimeout, CancellationToken cancellationToken)
|
||||
{
|
||||
if (millisecondsTimeout == Timeout.Infinite)
|
||||
{
|
||||
return (asyncWaiter.WaitAsync(cancellationToken));
|
||||
}
|
||||
else
|
||||
{
|
||||
return (asyncWaiter.WaitAsync(TimeSpan.FromMilliseconds(millisecondsTimeout), cancellationToken));
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
/// <summary>
|
||||
/// 进入锁
|
||||
/// </summary>
|
||||
public Task<bool> 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;
|
||||
|
@@ -0,0 +1,15 @@
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace PooledAwait
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides async/await-related extension methods
|
||||
/// </summary>
|
||||
public static class AwaitableExtensions
|
||||
{
|
||||
/// <summary>Controls whether a yield operation should respect captured context</summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static ConfiguredYieldAwaitable ConfigureAwait(this YieldAwaitable _, bool continueOnCapturedContext)
|
||||
=> new ConfiguredYieldAwaitable(continueOnCapturedContext);
|
||||
}
|
||||
}
|
@@ -0,0 +1,125 @@
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace PooledAwait
|
||||
{
|
||||
/// <summary>Provides an awaitable context for switching into a target environment.</summary>
|
||||
public readonly struct ConfiguredYieldAwaitable
|
||||
{
|
||||
/// <summary><see cref="Object.Equals(Object)"/></summary>
|
||||
public override bool Equals(object? obj) => obj is ConfiguredYieldAwaitable other && other._continueOnCapturedContext == _continueOnCapturedContext;
|
||||
/// <summary><see cref="Object.GetHashCode"/></summary>
|
||||
public override int GetHashCode() => _continueOnCapturedContext ? 1 : 0;
|
||||
/// <summary><see cref="Object.ToString"/></summary>
|
||||
public override string ToString() => nameof(ConfiguredYieldAwaitable);
|
||||
|
||||
private readonly bool _continueOnCapturedContext;
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
internal ConfiguredYieldAwaitable(bool continueOnCapturedContext)
|
||||
=> _continueOnCapturedContext = continueOnCapturedContext;
|
||||
|
||||
/// <summary>Gets an awaiter for this <see cref="ConfiguredYieldAwaitable"/>.</summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public ConfiguredYieldAwaiter GetAwaiter()
|
||||
=> new ConfiguredYieldAwaiter(_continueOnCapturedContext);
|
||||
|
||||
/// <summary>Provides an awaitable context for switching into a target environment.</summary>
|
||||
public readonly struct ConfiguredYieldAwaiter : ICriticalNotifyCompletion, INotifyCompletion
|
||||
{
|
||||
/// <summary><see cref="Object.Equals(Object)"/></summary>
|
||||
public override bool Equals(object? obj) => obj is ConfiguredYieldAwaiter other && other._continueOnCapturedContext == _continueOnCapturedContext;
|
||||
/// <summary><see cref="Object.GetHashCode"/></summary>
|
||||
public override int GetHashCode() => _continueOnCapturedContext ? 1 : 0;
|
||||
/// <summary><see cref="Object.ToString"/></summary>
|
||||
public override string ToString() => nameof(ConfiguredYieldAwaiter);
|
||||
|
||||
private readonly bool _continueOnCapturedContext;
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
internal ConfiguredYieldAwaiter(bool continueOnCapturedContext)
|
||||
=> _continueOnCapturedContext = continueOnCapturedContext;
|
||||
|
||||
/// <summary>Gets whether a yield is not required.</summary>
|
||||
public bool IsCompleted
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get => false;
|
||||
}
|
||||
|
||||
/// <summary>Ends the await operation.</summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void GetResult() { }
|
||||
|
||||
/// <summary>Posts the <paramref name="continuation"/> back to the current context.</summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void OnCompleted(Action continuation)
|
||||
{
|
||||
if (_continueOnCapturedContext) YieldFlowContext(continuation, true);
|
||||
else YieldNoContext(continuation, true);
|
||||
}
|
||||
|
||||
/// <summary>Posts the <paramref name="continuation"/> back to the current context.</summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void UnsafeOnCompleted(Action continuation)
|
||||
{
|
||||
if (_continueOnCapturedContext) YieldFlowContext(continuation, false);
|
||||
else YieldNoContext(continuation, false);
|
||||
}
|
||||
|
||||
private static readonly WaitCallback s_waitCallbackRunAction = state => ((Action?)state)?.Invoke();
|
||||
|
||||
#if PLAT_THREADPOOLWORKITEM
|
||||
private sealed class ContinuationWorkItem : IThreadPoolWorkItem
|
||||
{
|
||||
private Action? _continuation;
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private ContinuationWorkItem() => Internal.Counters.ItemBoxAllocated.Increment();
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static ContinuationWorkItem Create(Action continuation)
|
||||
{
|
||||
var box = Pool<ContinuationWorkItem>.TryGet() ?? new ContinuationWorkItem();
|
||||
box._continuation = continuation;
|
||||
return box;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
void IThreadPoolWorkItem.Execute()
|
||||
{
|
||||
var callback = _continuation;
|
||||
_continuation = null;
|
||||
Pool<ContinuationWorkItem>.TryPut(this);
|
||||
callback?.Invoke();
|
||||
}
|
||||
}
|
||||
#endif
|
||||
[MethodImpl(MethodImplOptions.NoInlining)] // no-one ever calls ConfigureAwait(true)!
|
||||
private static void YieldFlowContext(Action continuation, bool flowContext)
|
||||
{
|
||||
var awaiter = default(YieldAwaitable.YieldAwaiter);
|
||||
if (flowContext) awaiter.OnCompleted(continuation);
|
||||
else awaiter.UnsafeOnCompleted(continuation);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static void YieldNoContext(Action continuation, bool flowContext)
|
||||
{
|
||||
if (flowContext)
|
||||
{
|
||||
ThreadPool.QueueUserWorkItem(s_waitCallbackRunAction, continuation);
|
||||
}
|
||||
else
|
||||
{
|
||||
#if PLAT_THREADPOOLWORKITEM
|
||||
ThreadPool.UnsafeQueueUserWorkItem(ContinuationWorkItem.Create(continuation), false);
|
||||
#elif NETSTANDARD1_3
|
||||
ThreadPool.QueueUserWorkItem(s_waitCallbackRunAction, continuation);
|
||||
#else
|
||||
ThreadPool.UnsafeQueueUserWorkItem(s_waitCallbackRunAction, continuation);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,62 @@
|
||||
using PooledAwait.Internal;
|
||||
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace PooledAwait
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents an operation that completes at the first incomplete await,
|
||||
/// with the remainder continuing in the background
|
||||
/// </summary>
|
||||
[AsyncMethodBuilder(typeof(MethodBuilders.FireAndForgetMethodBuilder))]
|
||||
public readonly struct FireAndForget
|
||||
{
|
||||
/// <summary><see cref="Object.Equals(Object)"/></summary>
|
||||
public override bool Equals(object? obj) => ThrowHelper.ThrowNotSupportedException<bool>();
|
||||
/// <summary><see cref="Object.GetHashCode"/></summary>
|
||||
public override int GetHashCode() => ThrowHelper.ThrowNotSupportedException<int>();
|
||||
/// <summary><see cref="Object.ToString"/></summary>
|
||||
public override string ToString() => nameof(FireAndForget);
|
||||
|
||||
/// <summary>
|
||||
/// Raised when exceptions occur on fire-and-forget methods
|
||||
/// </summary>
|
||||
public static event Action<Exception>? Exception;
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
internal static void OnException(Exception exception)
|
||||
{
|
||||
if (exception != null) Exception?.Invoke(exception);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the instance as a value-task
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public ValueTask AsValueTask() => default;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the instance as a task
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public Task AsTask() => TaskUtils.CompletedTask;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the instance as a task
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static implicit operator Task(FireAndForget _) => TaskUtils.CompletedTask;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the instance as a value-task
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static implicit operator ValueTask(FireAndForget _) => default;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the awaiter for the instance
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public ValueTaskAwaiter GetAwaiter() => default(ValueTask).GetAwaiter();
|
||||
}
|
||||
}
|
13
src/Admin/ThingsGateway.NewLife.X/PooledAwait/IResettable.cs
Normal file
13
src/Admin/ThingsGateway.NewLife.X/PooledAwait/IResettable.cs
Normal file
@@ -0,0 +1,13 @@
|
||||
namespace PooledAwait
|
||||
{
|
||||
/// <summary>
|
||||
/// Indicates that an object can be reset
|
||||
/// </summary>
|
||||
public interface IResettable
|
||||
{
|
||||
/// <summary>
|
||||
/// Resets this instance
|
||||
/// </summary>
|
||||
public void Reset();
|
||||
}
|
||||
}
|
@@ -0,0 +1,12 @@
|
||||
#if NETSTANDARD1_3
|
||||
using System;
|
||||
|
||||
namespace PooledAwait.Internal
|
||||
{
|
||||
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Property | AttributeTargets.Class | AttributeTargets.Struct, AllowMultiple = false, Inherited = true)]
|
||||
internal sealed class BrowsableAttribute : Attribute
|
||||
{
|
||||
public BrowsableAttribute(bool _) { }
|
||||
}
|
||||
}
|
||||
#endif
|
@@ -0,0 +1,68 @@
|
||||
using System.Diagnostics;
|
||||
|
||||
namespace PooledAwait.Internal
|
||||
{
|
||||
internal static class Counters
|
||||
{
|
||||
internal struct Counter
|
||||
{
|
||||
private long _value;
|
||||
[Conditional("DEBUG")]
|
||||
public void Increment() => Interlocked.Increment(ref _value);
|
||||
#if !DEBUG
|
||||
[System.Obsolete("Release only", false)]
|
||||
#endif
|
||||
public long Value => Interlocked.Read(ref _value);
|
||||
#pragma warning disable CS0618
|
||||
public override string ToString() => Value.ToString();
|
||||
#pragma warning restore CS0618
|
||||
public override bool Equals(object? obj) => ThrowHelper.ThrowNotSupportedException<bool>();
|
||||
public override int GetHashCode() => ThrowHelper.ThrowNotSupportedException<int>();
|
||||
public void Reset() => Interlocked.Exchange(ref _value, 0);
|
||||
}
|
||||
|
||||
internal static Counter
|
||||
SetStateMachine,
|
||||
PooledStateAllocated,
|
||||
PooledStateRecycled,
|
||||
StateMachineBoxAllocated,
|
||||
StateMachineBoxRecycled,
|
||||
ItemBoxAllocated,
|
||||
TaskAllocated,
|
||||
LazyStateAllocated;
|
||||
|
||||
#if !DEBUG
|
||||
[System.Obsolete("Release only", false)]
|
||||
#endif
|
||||
public static long TotalAllocations =>
|
||||
PooledStateAllocated.Value + StateMachineBoxAllocated.Value
|
||||
+ ItemBoxAllocated.Value + TaskAllocated.Value
|
||||
+ SetStateMachine.Value // SetStateMachine usually means a boxed value
|
||||
+ LazyStateAllocated.Value;
|
||||
|
||||
internal static void Reset()
|
||||
{
|
||||
SetStateMachine.Reset();
|
||||
PooledStateAllocated.Reset();
|
||||
PooledStateRecycled.Reset();
|
||||
StateMachineBoxAllocated.Reset();
|
||||
StateMachineBoxRecycled.Reset();
|
||||
ItemBoxAllocated.Reset();
|
||||
TaskAllocated.Reset();
|
||||
LazyStateAllocated.Reset();
|
||||
}
|
||||
|
||||
#if !DEBUG
|
||||
[System.Obsolete("Release only", false)]
|
||||
#endif
|
||||
internal static string Summary()
|
||||
=> $@"SetStateMachine: {SetStateMachine.Value}
|
||||
PooledStateAllocated: {PooledStateAllocated.Value}
|
||||
PooledStateRecycled: {PooledStateRecycled.Value}
|
||||
StateMachineBoxAllocated: {StateMachineBoxAllocated.Value}
|
||||
StateMachineBoxRecycled: {StateMachineBoxRecycled.Value}
|
||||
ItemBoxAllocated: {ItemBoxAllocated.Value}
|
||||
TaskAllocated: {TaskAllocated.Value}
|
||||
LazyStateAllocated: {LazyStateAllocated.Value}";
|
||||
}
|
||||
}
|
@@ -0,0 +1,173 @@
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace PooledAwait.Internal
|
||||
{
|
||||
internal sealed class LazyTaskState<T>
|
||||
{
|
||||
private short _version;
|
||||
private T _result;
|
||||
private Exception? _exception;
|
||||
private Task? _task;
|
||||
private bool _isComplete;
|
||||
private ValueTaskCompletionSource<T> _source;
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private void CheckTokenInsideLock(short token)
|
||||
{
|
||||
if (token != _version) ThrowHelper.ThrowInvalidOperationException();
|
||||
}
|
||||
private object _syncRoot = new object();
|
||||
public Task GetTask(short token)
|
||||
{
|
||||
lock (_syncRoot)
|
||||
{
|
||||
CheckTokenInsideLock(token);
|
||||
if (_task != null) { }
|
||||
else if (_exception is OperationCanceledException) _task = TaskUtils.TaskFactory<T>.Canceled;
|
||||
else if (_exception != null) _task = TaskUtils.FromException<T>(_exception);
|
||||
else if (_isComplete) _task = typeof(T) == typeof(Nothing) ? TaskUtils.CompletedTask : TaskUtils.TaskFactory<T>.FromResult(_result);
|
||||
else
|
||||
{
|
||||
_source = ValueTaskCompletionSource<T>.Create();
|
||||
_task = _source.Task;
|
||||
}
|
||||
return _task;
|
||||
}
|
||||
}
|
||||
|
||||
internal bool IsValid(short token) => Volatile.Read(ref _version) == token;
|
||||
internal bool HasSource
|
||||
{
|
||||
get
|
||||
{
|
||||
lock (_syncRoot) { return !_source.IsNull; }
|
||||
}
|
||||
}
|
||||
internal bool HasTask => Volatile.Read(ref _task) != null;
|
||||
|
||||
public bool TrySetResult(short token, T result)
|
||||
{
|
||||
lock (_syncRoot)
|
||||
{
|
||||
if (_isComplete) return false;
|
||||
if (token != _version) return false;
|
||||
_isComplete = true;
|
||||
if (!_source.IsNull) return _source.TrySetResult(result);
|
||||
_result = result;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
public bool TrySetException(short token, Exception exception)
|
||||
{
|
||||
lock (_syncRoot)
|
||||
{
|
||||
if (_isComplete) return false;
|
||||
if (token != _version) return false;
|
||||
_isComplete = true;
|
||||
if (!_source.IsNull) return _source.TrySetException(exception);
|
||||
_exception = exception;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
public bool TrySetCanceled(short token, CancellationToken cancellationToken = default)
|
||||
{
|
||||
lock (_syncRoot)
|
||||
{
|
||||
if (_isComplete) return false;
|
||||
if (token != _version) return false;
|
||||
_isComplete = true;
|
||||
if (!_source.IsNull) return _source.TrySetCanceled(cancellationToken);
|
||||
_task = TaskUtils.TaskFactory<T>.Canceled;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static LazyTaskState<T> Create() => Pool<LazyTaskState<T>>.TryGet() ?? new LazyTaskState<T>();
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private LazyTaskState()
|
||||
{
|
||||
Counters.LazyStateAllocated.Increment();
|
||||
_result = default!;
|
||||
_version = InitialVersion;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static LazyTaskState<T> CreateConstant(T value)
|
||||
{
|
||||
var obj = new LazyTaskState<T>
|
||||
{
|
||||
_version = Constant
|
||||
};
|
||||
obj.TrySetResult(Constant, value);
|
||||
return obj;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static LazyTaskState<T> CreateCanceled()
|
||||
{
|
||||
var obj = new LazyTaskState<T>
|
||||
{
|
||||
_version = Constant
|
||||
};
|
||||
obj.TrySetCanceled(Constant);
|
||||
return obj;
|
||||
}
|
||||
|
||||
const short InitialVersion = 0, Constant = InitialVersion - 1;
|
||||
|
||||
internal short Version
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get => _version;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||
internal void Recycle(short token)
|
||||
{
|
||||
if (token == Constant) return; // never recycle constant values; this is by design
|
||||
|
||||
if (Volatile.Read(ref _version) != token) return; // wrong version; all bets are off!
|
||||
|
||||
if (!Volatile.Read(ref _isComplete)) // if incomplete, try to cancel
|
||||
{
|
||||
if (!TrySetCanceled(token)) return; // if that didn't work... give up - don't recycle
|
||||
}
|
||||
|
||||
bool haveLock = false;
|
||||
try
|
||||
{
|
||||
// only if uncontested; we're not waiting in a dispose
|
||||
Monitor.TryEnter(_syncRoot, ref haveLock);
|
||||
if (haveLock)
|
||||
{
|
||||
if (token == _version)
|
||||
{
|
||||
_result = default!;
|
||||
_exception = default;
|
||||
_task = default;
|
||||
_isComplete = false;
|
||||
_source = default;
|
||||
|
||||
switch (++_version)
|
||||
{
|
||||
case InitialVersion: // don't wrap all the way around when recycling; could lead to conflicts
|
||||
case Constant: // don't allow things to *become* constants
|
||||
break;
|
||||
default:
|
||||
Pool<LazyTaskState<T>>.TryPut(this);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (haveLock) Monitor.Exit(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,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<object?>? Continuation;
|
||||
public readonly object? State;
|
||||
private WaitCallbackShim(Action<object?>? continuation, object? state)
|
||||
{
|
||||
Continuation = continuation;
|
||||
State = state;
|
||||
}
|
||||
public static object Create(Action<object?>? continuation, object? state)
|
||||
=> Pool.Box(new WaitCallbackShim(continuation, state));
|
||||
|
||||
private void InvokeContinuation() => Continuation?.Invoke(State);
|
||||
|
||||
public static readonly WaitCallback Invoke = state => Pool.UnboxAndReturn<WaitCallbackShim>(state).InvokeContinuation();
|
||||
}
|
||||
|
||||
/// <summary>Provides the core logic for implementing a manual-reset <see cref="IValueTaskSource"/> or <see cref="IValueTaskSource{TResult}"/>.</summary>
|
||||
/// <typeparam name="TResult"></typeparam>
|
||||
[StructLayout(LayoutKind.Auto)]
|
||||
public struct ManualResetValueTaskSourceCore<TResult>
|
||||
{
|
||||
/// <summary>
|
||||
/// The callback to invoke when the operation completes if <see cref="OnCompleted"/> was called before the operation completed,
|
||||
/// or <see cref="ManualResetValueTaskSourceCoreShared.s_sentinel"/> if the operation completed before a callback was supplied,
|
||||
/// or null if a callback hasn't yet been provided and the operation hasn't yet completed.
|
||||
/// </summary>
|
||||
private Action<object?>? _continuation;
|
||||
/// <summary>State to pass to <see cref="_continuation"/>.</summary>
|
||||
private object? _continuationState;
|
||||
/// <summary><see cref="ExecutionContext"/> to flow to the callback, or null if no flowing is required.</summary>
|
||||
private ExecutionContext? _executionContext;
|
||||
/// <summary>
|
||||
/// A "captured" <see cref="SynchronizationContext"/> or <see cref="TaskScheduler"/> with which to invoke the callback,
|
||||
/// or null if no special context is required.
|
||||
/// </summary>
|
||||
private object? _capturedContext;
|
||||
/// <summary>Whether the current operation has completed.</summary>
|
||||
private bool _completed;
|
||||
/// <summary>The result with which the operation succeeded, or the default value if it hasn't yet completed or failed.</summary>
|
||||
/* [AllowNull, MaybeNull] */ private TResult _result;
|
||||
/// <summary>The exception with which the operation failed, or null if it hasn't yet completed or completed successfully.</summary>
|
||||
private ExceptionDispatchInfo? _error;
|
||||
/// <summary>The current version of this value, used to help prevent misuse.</summary>
|
||||
private short _version;
|
||||
|
||||
/// <summary>Gets or sets whether to force continuations to run asynchronously.</summary>
|
||||
/// <remarks>Continuations may run asynchronously if this is false, but they'll never run synchronously if this is true.</remarks>
|
||||
public bool RunContinuationsAsynchronously { get; set; }
|
||||
|
||||
/// <summary>Resets to prepare for the next operation.</summary>
|
||||
public void Reset()
|
||||
{
|
||||
// Reset/update state for the next use/await of this instance.
|
||||
_version++;
|
||||
_completed = false;
|
||||
_result = default!; // TODO-NULLABLE: Remove ! when nullable attributes are respected
|
||||
_error = null;
|
||||
_executionContext = null;
|
||||
_capturedContext = null;
|
||||
_continuation = null;
|
||||
_continuationState = null;
|
||||
}
|
||||
|
||||
/// <summary>Completes with a successful result.</summary>
|
||||
/// <param name="result">The result.</param>
|
||||
public void SetResult(TResult result)
|
||||
{
|
||||
_result = result;
|
||||
SignalCompletion();
|
||||
}
|
||||
|
||||
/// <summary>Complets with an error.</summary>
|
||||
/// <param name="error"></param>
|
||||
public void SetException(Exception error)
|
||||
{
|
||||
_error = ExceptionDispatchInfo.Capture(error);
|
||||
SignalCompletion();
|
||||
}
|
||||
|
||||
/// <summary>Gets the operation version.</summary>
|
||||
public short Version => _version;
|
||||
|
||||
/// <summary>Gets the status of the operation.</summary>
|
||||
/// <param name="token">Opaque value that was provided to the <see cref="ValueTask"/>'s constructor.</param>
|
||||
public ValueTaskSourceStatus GetStatus(short token)
|
||||
{
|
||||
ValidateToken(token);
|
||||
return
|
||||
_continuation == null || !_completed ? ValueTaskSourceStatus.Pending :
|
||||
_error == null ? ValueTaskSourceStatus.Succeeded :
|
||||
_error.SourceException is OperationCanceledException ? ValueTaskSourceStatus.Canceled :
|
||||
ValueTaskSourceStatus.Faulted;
|
||||
}
|
||||
|
||||
/// <summary>Gets the result of the operation.</summary>
|
||||
/// <param name="token">Opaque value that was provided to the <see cref="ValueTask"/>'s constructor.</param>
|
||||
// [StackTraceHidden]
|
||||
public TResult GetResult(short token)
|
||||
{
|
||||
ValidateToken(token);
|
||||
if (!_completed)
|
||||
{
|
||||
ThrowHelper.ThrowInvalidOperationException();
|
||||
}
|
||||
|
||||
_error?.Throw();
|
||||
return _result;
|
||||
}
|
||||
|
||||
/// <summary>Schedules the continuation action for this operation.</summary>
|
||||
/// <param name="continuation">The continuation to invoke when the operation has completed.</param>
|
||||
/// <param name="state">The state object to pass to <paramref name="continuation"/> when it's invoked.</param>
|
||||
/// <param name="token">Opaque value that was provided to the <see cref="ValueTask"/>'s constructor.</param>
|
||||
/// <param name="flags">The flags describing the behavior of the continuation.</param>
|
||||
public void OnCompleted(Action<object?> continuation, object? state, short token, ValueTaskSourceOnCompletedFlags flags)
|
||||
{
|
||||
if (continuation == null) ThrowHelper.ThrowArgumentNullException(nameof(continuation));
|
||||
ValidateToken(token);
|
||||
|
||||
if ((flags & ValueTaskSourceOnCompletedFlags.FlowExecutionContext) != 0)
|
||||
{
|
||||
_executionContext = ExecutionContext.Capture();
|
||||
}
|
||||
|
||||
if ((flags & ValueTaskSourceOnCompletedFlags.UseSchedulingContext) != 0)
|
||||
{
|
||||
SynchronizationContext? sc = SynchronizationContext.Current;
|
||||
if (sc != null && sc.GetType() != typeof(SynchronizationContext))
|
||||
{
|
||||
_capturedContext = sc;
|
||||
}
|
||||
else
|
||||
{
|
||||
TaskScheduler ts = TaskScheduler.Current;
|
||||
if (ts != TaskScheduler.Default)
|
||||
{
|
||||
_capturedContext = ts;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// We need to set the continuation state before we swap in the delegate, so that
|
||||
// if there's a race between this and SetResult/Exception and SetResult/Exception
|
||||
// sees the _continuation as non-null, it'll be able to invoke it with the state
|
||||
// stored here. However, this also means that if this is used incorrectly (e.g.
|
||||
// awaited twice concurrently), _continuationState might get erroneously overwritten.
|
||||
// To minimize the chances of that, we check preemptively whether _continuation
|
||||
// is already set to something other than the completion sentinel.
|
||||
|
||||
object? oldContinuation = _continuation;
|
||||
if (oldContinuation == null)
|
||||
{
|
||||
_continuationState = state;
|
||||
oldContinuation = Interlocked.CompareExchange(ref _continuation, continuation, null);
|
||||
}
|
||||
|
||||
if (oldContinuation != null)
|
||||
{
|
||||
// Operation already completed, so we need to queue the supplied callback.
|
||||
if (!ReferenceEquals(oldContinuation, ManualResetValueTaskSourceCoreShared.s_sentinel))
|
||||
{
|
||||
ThrowHelper.ThrowInvalidOperationException();
|
||||
}
|
||||
|
||||
switch (_capturedContext)
|
||||
{
|
||||
case null:
|
||||
if (_executionContext != null)
|
||||
{
|
||||
ThreadPool.QueueUserWorkItem(WaitCallbackShim.Invoke, WaitCallbackShim.Create(continuation, state));
|
||||
}
|
||||
else
|
||||
{
|
||||
#if NETSTANDARD1_3
|
||||
ThreadPool.QueueUserWorkItem(
|
||||
#else
|
||||
ThreadPool.UnsafeQueueUserWorkItem(
|
||||
#endif
|
||||
WaitCallbackShim.Invoke, WaitCallbackShim.Create(continuation, state));
|
||||
}
|
||||
break;
|
||||
|
||||
case SynchronizationContext sc:
|
||||
sc.Post(s =>
|
||||
{
|
||||
var tuple = (Tuple<Action<object?>, object?>)s!;
|
||||
tuple.Item1(tuple.Item2);
|
||||
}, Tuple.Create(continuation, state));
|
||||
break;
|
||||
|
||||
case TaskScheduler ts:
|
||||
Task.Factory.StartNew(continuation, state, CancellationToken.None, TaskCreationOptions.DenyChildAttach, ts);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Ensures that the specified token matches the current version.</summary>
|
||||
/// <param name="token">The token supplied by <see cref="ValueTask"/>.</param>
|
||||
private void ValidateToken(short token)
|
||||
{
|
||||
if (token != _version)
|
||||
{
|
||||
ThrowHelper.ThrowInvalidOperationException();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Signals that the operation has completed. Invoked after the result or error has been set.</summary>
|
||||
private void SignalCompletion()
|
||||
{
|
||||
if (_completed)
|
||||
{
|
||||
ThrowHelper.ThrowInvalidOperationException();
|
||||
}
|
||||
_completed = true;
|
||||
|
||||
if (_continuation != null || Interlocked.CompareExchange(ref _continuation, ManualResetValueTaskSourceCoreShared.s_sentinel, null) != null)
|
||||
{
|
||||
if (_executionContext != null)
|
||||
{
|
||||
ExecutionContext.Run(
|
||||
_executionContext,
|
||||
s_UnboxAndInvokeContextCallback,
|
||||
Pool.Box<ManualResetValueTaskSourceCore<TResult>>(this));
|
||||
}
|
||||
else
|
||||
{
|
||||
InvokeContinuation();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static readonly ContextCallback s_UnboxAndInvokeContextCallback = state => Pool.UnboxAndReturn<ManualResetValueTaskSourceCore<TResult>>(state).InvokeContinuation();
|
||||
|
||||
/// <summary>
|
||||
/// Invokes the continuation with the appropriate captured context / scheduler.
|
||||
/// This assumes that if <see cref="_executionContext"/> is not null we're already
|
||||
/// running within that <see cref="ExecutionContext"/>.
|
||||
/// </summary>
|
||||
private void InvokeContinuation()
|
||||
{
|
||||
Debug.Assert(_continuation != null);
|
||||
|
||||
switch (_capturedContext)
|
||||
{
|
||||
case null:
|
||||
if (RunContinuationsAsynchronously)
|
||||
{
|
||||
if (_executionContext != null)
|
||||
{
|
||||
ThreadPool.QueueUserWorkItem(WaitCallbackShim.Invoke, WaitCallbackShim.Create(_continuation, _continuationState));
|
||||
}
|
||||
else
|
||||
{
|
||||
#if NETSTANDARD1_3
|
||||
ThreadPool.QueueUserWorkItem(
|
||||
#else
|
||||
ThreadPool.UnsafeQueueUserWorkItem(
|
||||
#endif
|
||||
WaitCallbackShim.Invoke, WaitCallbackShim.Create(_continuation, _continuationState));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
_continuation!(_continuationState);
|
||||
}
|
||||
break;
|
||||
|
||||
case SynchronizationContext sc:
|
||||
sc.Post(s =>
|
||||
{
|
||||
var state = (Tuple<Action<object?>, object?>)s!;
|
||||
state.Item1(state.Item2);
|
||||
}, Tuple.Create(_continuation, _continuationState));
|
||||
break;
|
||||
|
||||
case TaskScheduler ts:
|
||||
Task.Factory.StartNew(_continuation, _continuationState, CancellationToken.None, TaskCreationOptions.DenyChildAttach, ts);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal static class ManualResetValueTaskSourceCoreShared // separated out of generic to avoid unnecessary duplication
|
||||
{
|
||||
internal static readonly Action<object?> s_sentinel = CompletionSentinel;
|
||||
private static void CompletionSentinel(object? _) // named method to aid debugging
|
||||
{
|
||||
Debug.Fail("The sentinel delegate should never be invoked.");
|
||||
ThrowHelper.ThrowInvalidOperationException();
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
@@ -0,0 +1,9 @@
|
||||
namespace PooledAwait.Internal
|
||||
{
|
||||
internal readonly struct Nothing // to express ValueTask via PooledState<Nothing>
|
||||
{
|
||||
public override string ToString() => nameof(Nothing);
|
||||
public override int GetHashCode() => 0;
|
||||
public override bool Equals(object? obj) => obj is Nothing;
|
||||
}
|
||||
}
|
@@ -0,0 +1,124 @@
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Threading.Tasks.Sources;
|
||||
|
||||
namespace PooledAwait.Internal
|
||||
{
|
||||
internal sealed class PooledState<T> : IValueTaskSource<T>, IValueTaskSource
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static PooledState<T> Create(out short token)
|
||||
{
|
||||
var obj = Pool<PooledState<T>>.TryGet() ?? new PooledState<T>();
|
||||
token = obj._source.Version;
|
||||
return obj;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private PooledState() => Counters.PooledStateAllocated.Increment();
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
internal bool IsValid(short token) => _source.Version == token;
|
||||
|
||||
private ManualResetValueTaskSourceCore<T> _source; // needs to be mutable
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public T GetResult(short token)
|
||||
{
|
||||
// we only support getting the result once; doing this recycles the source and advances the token
|
||||
|
||||
lock (SyncLock) // we need to be really paranoid about cross-threading over changing the token
|
||||
{
|
||||
var status = _source.GetStatus(token); // do this *outside* the try/finally
|
||||
try // so that we don't increment the counter if someone asks for the wrong value
|
||||
{
|
||||
switch (status)
|
||||
{
|
||||
case ValueTaskSourceStatus.Canceled:
|
||||
ThrowHelper.ThrowTaskCanceledException();
|
||||
break;
|
||||
case ValueTaskSourceStatus.Pending:
|
||||
Monitor.Wait(SyncLock);
|
||||
break;
|
||||
}
|
||||
return _source.GetResult(token);
|
||||
}
|
||||
finally
|
||||
{
|
||||
_source.Reset();
|
||||
if (_source.Version != TaskUtils.InitialTaskSourceVersion)
|
||||
{
|
||||
Pool<PooledState<T>>.TryPut(this);
|
||||
Counters.PooledStateRecycled.Increment();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
void IValueTaskSource.GetResult(short token) => GetResult(token);
|
||||
|
||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||
private void SignalResult(short token)
|
||||
{
|
||||
lock (SyncLock)
|
||||
{
|
||||
if (token == _source.Version && _source.GetStatus(token) != ValueTaskSourceStatus.Pending)
|
||||
{
|
||||
Monitor.Pulse(SyncLock);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public ValueTaskSourceStatus GetStatus(short token) => _source.GetStatus(token);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void OnCompleted(Action<object?> continuation, object? state, short token, ValueTaskSourceOnCompletedFlags flags)
|
||||
=> _source.OnCompleted(continuation, state, token, flags);
|
||||
|
||||
private object SyncLock
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get => this;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public bool TrySetException(Exception error, short token)
|
||||
{
|
||||
if (token == _source.Version)
|
||||
{
|
||||
|
||||
switch (_source.GetStatus(token))
|
||||
{
|
||||
case ValueTaskSourceStatus.Pending:
|
||||
_source.SetException(error);
|
||||
// only need to signal if SetException didn't inline a handler
|
||||
if (token == _source.Version) SignalResult(token);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public bool TrySetResult(T result, short token)
|
||||
{
|
||||
if (token == _source.Version)
|
||||
{
|
||||
switch (_source.GetStatus(token))
|
||||
{
|
||||
case ValueTaskSourceStatus.Pending:
|
||||
_source.SetResult(result);
|
||||
// only need to signal if SetResult didn't inline a handler
|
||||
if (token == _source.Version) SignalResult(token);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public bool TrySetCanceled(short token)
|
||||
=> TrySetException(TaskUtils.SharedTaskCanceledException, token);
|
||||
}
|
||||
}
|
@@ -0,0 +1,113 @@
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace PooledAwait.Internal
|
||||
{
|
||||
internal sealed class StateMachineBox<TStateMachine>
|
||||
#if PLAT_THREADPOOLWORKITEM
|
||||
: IThreadPoolWorkItem
|
||||
#endif
|
||||
where TStateMachine : IAsyncStateMachine
|
||||
{
|
||||
private readonly Action _execute;
|
||||
private TStateMachine _stateMachine;
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private StateMachineBox()
|
||||
{
|
||||
_stateMachine = default!;
|
||||
_execute = Execute;
|
||||
Counters.StateMachineBoxAllocated.Increment();
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static StateMachineBox<TStateMachine> Create(ref TStateMachine stateMachine)
|
||||
{
|
||||
var box = Pool<StateMachineBox<TStateMachine>>.TryGet() ?? new StateMachineBox<TStateMachine>();
|
||||
box._stateMachine = stateMachine;
|
||||
return box;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void AwaitOnCompleted<TAwaiter>(
|
||||
ref TAwaiter awaiter, ref TStateMachine stateMachine)
|
||||
where TAwaiter : INotifyCompletion
|
||||
{
|
||||
var box = Create(ref stateMachine);
|
||||
if (typeof(TAwaiter) == typeof(YieldAwaitable.YieldAwaiter))
|
||||
{
|
||||
Yield(box, true);
|
||||
}
|
||||
else
|
||||
{
|
||||
awaiter.OnCompleted(box._execute);
|
||||
}
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void AwaitUnsafeOnCompleted<TAwaiter>(
|
||||
ref TAwaiter awaiter, ref TStateMachine stateMachine)
|
||||
where TAwaiter : ICriticalNotifyCompletion
|
||||
{
|
||||
var box = Create(ref stateMachine);
|
||||
if (typeof(TAwaiter) == typeof(YieldAwaitable.YieldAwaiter))
|
||||
{
|
||||
Yield(box, false);
|
||||
}
|
||||
else
|
||||
{
|
||||
awaiter.UnsafeOnCompleted(box._execute);
|
||||
}
|
||||
}
|
||||
|
||||
private static void Yield(StateMachineBox<TStateMachine> box, bool flowContext)
|
||||
{
|
||||
// heavily inspired by YieldAwaitable.QueueContinuation
|
||||
|
||||
var syncContext = SynchronizationContext.Current;
|
||||
if (syncContext != null && syncContext.GetType() != typeof(SynchronizationContext))
|
||||
{
|
||||
syncContext.Post(s_SendOrPostCallback, box);
|
||||
}
|
||||
else
|
||||
{
|
||||
var taskScheduler = TaskScheduler.Current;
|
||||
if (!ReferenceEquals(taskScheduler, TaskScheduler.Default))
|
||||
{
|
||||
Task.Factory.StartNew(box._execute, default, TaskCreationOptions.PreferFairness, taskScheduler);
|
||||
}
|
||||
else if (flowContext)
|
||||
{
|
||||
ThreadPool.QueueUserWorkItem(s_WaitCallback, box);
|
||||
}
|
||||
else
|
||||
{
|
||||
#if PLAT_THREADPOOLWORKITEM
|
||||
ThreadPool.UnsafeQueueUserWorkItem(box, false);
|
||||
#elif NETSTANDARD1_3
|
||||
ThreadPool.QueueUserWorkItem(s_WaitCallback, box);
|
||||
#else
|
||||
ThreadPool.UnsafeQueueUserWorkItem(s_WaitCallback, box);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static readonly SendOrPostCallback s_SendOrPostCallback = state => ((StateMachineBox<TStateMachine>?)state!)?.Execute();
|
||||
static readonly WaitCallback s_WaitCallback = state => ((StateMachineBox<TStateMachine>?)state)?.Execute();
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void Execute()
|
||||
{
|
||||
// extract the state
|
||||
var tmp = _stateMachine;
|
||||
|
||||
// recycle the instance
|
||||
_stateMachine = default!;
|
||||
Pool<StateMachineBox<TStateMachine>>.TryPut(this);
|
||||
Counters.StateMachineBoxRecycled.Increment();
|
||||
|
||||
// progress the state machine
|
||||
tmp.MoveNext();
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,171 @@
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Threading.Tasks.Sources;
|
||||
|
||||
namespace PooledAwait.Internal
|
||||
{
|
||||
// NET45 lacks some useful Task APIs; shim over them
|
||||
internal static class TaskUtils
|
||||
{
|
||||
internal static readonly short InitialTaskSourceVersion = new ManualResetValueTaskSourceCore<Nothing>().Version;
|
||||
|
||||
public static readonly TaskCanceledException SharedTaskCanceledException = new TaskCanceledException();
|
||||
#if NET45
|
||||
public static readonly Task CompletedTask = TaskFactory<Nothing>.FromResult(default);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static Task<T> FromException<T>(Exception exception)
|
||||
{
|
||||
var source = ValueTaskCompletionSource<T>.Create();
|
||||
source.TrySetException(exception);
|
||||
return source.Task;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static Task FromException(Exception exception) => FromException<bool>(exception);
|
||||
#else
|
||||
public static readonly Task CompletedTask = Task.CompletedTask;
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static Task<T> FromException<T>(Exception exception) => Task.FromException<T>(exception);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static Task FromException(Exception exception) => Task.FromException(exception);
|
||||
#endif
|
||||
|
||||
|
||||
internal static class TaskFactory<TResult>
|
||||
{
|
||||
// draws from AsyncMethodBuilder, but less boxing
|
||||
|
||||
public static readonly Task<TResult> Canceled = CreateCanceled();
|
||||
|
||||
static Task<TResult> CreateCanceled()
|
||||
{
|
||||
var source = ValueTaskCompletionSource<TResult>.Create();
|
||||
source.TrySetCanceled();
|
||||
return source.Task;
|
||||
}
|
||||
|
||||
private static readonly TaskCache<TResult> _cache = (TaskCache<TResult>)CreateCacheForType();
|
||||
|
||||
private static object CreateCacheForType()
|
||||
{
|
||||
if (typeof(TResult) == typeof(Nothing)) return new NothingTaskCache();
|
||||
if (typeof(TResult) == typeof(int)) return new Int32TaskCache();
|
||||
if (typeof(TResult) == typeof(int?)) return new NullableInt32TaskCache();
|
||||
if (typeof(TResult) == typeof(bool)) return new BooleanTaskCache();
|
||||
if (typeof(TResult) == typeof(bool?)) return new NullableBooleanTaskCache();
|
||||
|
||||
Type underlyingType = Nullable.GetUnderlyingType(typeof(TResult)) ?? typeof(TResult);
|
||||
if (underlyingType == typeof(uint)
|
||||
|| underlyingType == typeof(byte)
|
||||
|| underlyingType == typeof(sbyte)
|
||||
|| underlyingType == typeof(char)
|
||||
|| underlyingType == typeof(decimal)
|
||||
|| underlyingType == typeof(long)
|
||||
|| underlyingType == typeof(ulong)
|
||||
|| underlyingType == typeof(short)
|
||||
|| underlyingType == typeof(ushort)
|
||||
|| underlyingType == typeof(float)
|
||||
|| underlyingType == typeof(double)
|
||||
|| underlyingType == typeof(IntPtr)
|
||||
|| underlyingType == typeof(UIntPtr)
|
||||
) return new DefaultEquatableTaskCache<TResult>();
|
||||
|
||||
if (typeof(TResult) == typeof(string)) return new StringTaskCache();
|
||||
#if !NETSTANDARD1_3
|
||||
if (!typeof(TResult).IsValueType) return new ObjectTaskCache<TResult>();
|
||||
#endif
|
||||
return new TaskCache<TResult>();
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static Task<TResult> FromResult(TResult result) => _cache.FromResult(result);
|
||||
}
|
||||
|
||||
class TaskCache<TResult>
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
internal virtual Task<TResult> FromResult(TResult value) => Task.FromResult(value);
|
||||
}
|
||||
class NothingTaskCache : TaskCache<Nothing>
|
||||
{
|
||||
private static readonly Task<Nothing> s_Instance = Task.FromResult<Nothing>(default);
|
||||
internal override Task<Nothing> FromResult(Nothing value) => s_Instance;
|
||||
}
|
||||
class DefaultEquatableTaskCache<TResult> : TaskCache<TResult>
|
||||
{
|
||||
private static readonly Task<TResult> s_Default = Task.FromResult<TResult>(default!);
|
||||
private static readonly EqualityComparer<TResult> _comparer = EqualityComparer<TResult>.Default;
|
||||
internal override Task<TResult> FromResult(TResult value)
|
||||
=> _comparer.Equals(value, default!) ? s_Default : base.FromResult(value);
|
||||
}
|
||||
class ObjectTaskCache<TResult> : TaskCache<TResult>
|
||||
{
|
||||
private static readonly Task<TResult> s_Null = Task.FromResult<TResult>(default!);
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
internal override Task<TResult> FromResult(TResult value)
|
||||
=> value == null ? s_Null : base.FromResult(value);
|
||||
}
|
||||
sealed class StringTaskCache : ObjectTaskCache<string>
|
||||
{
|
||||
private static readonly Task<string> s_Empty = Task.FromResult<string>("");
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
internal override Task<string> FromResult(string value)
|
||||
=> string.IsNullOrEmpty(value) ? s_Empty : base.FromResult(value);
|
||||
}
|
||||
sealed class BooleanTaskCache : TaskCache<bool>
|
||||
{
|
||||
static readonly Task<bool> s_True = Task.FromResult(true), s_False = Task.FromResult(false);
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
internal override Task<bool> FromResult(bool value) => value ? s_True : s_False;
|
||||
}
|
||||
sealed class NullableBooleanTaskCache : TaskCache<bool?>
|
||||
{
|
||||
static readonly Task<bool?> s_True = Task.FromResult((bool?)true), s_False = Task.FromResult((bool?)false),
|
||||
s_Null = Task.FromResult((bool?)null);
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
internal override Task<bool?> FromResult(bool? value) =>
|
||||
value.HasValue ? (value.GetValueOrDefault() ? s_True : s_False) : s_Null;
|
||||
}
|
||||
sealed class Int32TaskCache : TaskCache<int>
|
||||
{
|
||||
const int MIN_INC = -1, MAX_EXC = 11;
|
||||
static readonly Task<int>[] s_Known = CreateKnown();
|
||||
static Task<int>[] CreateKnown()
|
||||
{
|
||||
var arr = new Task<int>[MAX_EXC - MIN_INC];
|
||||
for (int i = 0; i < arr.Length; i++)
|
||||
arr[i] = Task.FromResult(i + MIN_INC);
|
||||
return arr;
|
||||
}
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
internal override Task<int> FromResult(int value)
|
||||
=> value >= MIN_INC && value < MAX_EXC ? s_Known[value - MIN_INC] : base.FromResult(value);
|
||||
}
|
||||
sealed class NullableInt32TaskCache : TaskCache<int?>
|
||||
{
|
||||
const int MIN_INC = -1, MAX_EXC = 11;
|
||||
static readonly Task<int?>[] s_Known = CreateKnown();
|
||||
static readonly Task<int?> s_Null = Task.FromResult((int?)null);
|
||||
static Task<int?>[] CreateKnown()
|
||||
{
|
||||
var arr = new Task<int?>[MAX_EXC - MIN_INC];
|
||||
for (int i = 0; i < arr.Length; i++)
|
||||
arr[i] = Task.FromResult((int?)(i + MIN_INC));
|
||||
return arr;
|
||||
}
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
internal override Task<int?> FromResult(int? nullable)
|
||||
{
|
||||
if (nullable.HasValue)
|
||||
{
|
||||
int value = nullable.GetValueOrDefault();
|
||||
return value >= MIN_INC && value < MAX_EXC ? s_Known[value - MIN_INC] : base.FromResult(nullable);
|
||||
}
|
||||
return s_Null;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
@@ -0,0 +1,30 @@
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace PooledAwait.Internal
|
||||
{
|
||||
internal static class ThrowHelper
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||
internal static void ThrowInvalidOperationException(string? message = null)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(message)) throw new InvalidOperationException();
|
||||
else throw new InvalidOperationException(message);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||
internal static T ThrowInvalidOperationException<T>(string? message = null)
|
||||
{
|
||||
ThrowInvalidOperationException(message);
|
||||
return default!;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||
internal static T ThrowNotSupportedException<T>() => throw new NotSupportedException();
|
||||
|
||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||
internal static void ThrowArgumentNullException(string paramName) => throw new ArgumentNullException(paramName);
|
||||
|
||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||
internal static void ThrowTaskCanceledException() => throw new TaskCanceledException();
|
||||
}
|
||||
}
|
@@ -0,0 +1,127 @@
|
||||
using PooledAwait.Internal;
|
||||
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace PooledAwait
|
||||
{
|
||||
/// <summary>
|
||||
/// Like a ValueTaskCompletionSource, but the actual task will only become allocated
|
||||
/// if the .Task is consumed; this is useful for APIs where the consumer *may* consume a task
|
||||
/// </summary>
|
||||
public readonly struct LazyTaskCompletionSource : IDisposable
|
||||
{
|
||||
|
||||
private static LazyTaskCompletionSource _completed, _canceled;
|
||||
|
||||
/// <summary>
|
||||
/// A global LazyTaskCompletionSource that represents a completed operation
|
||||
/// </summary>
|
||||
public static LazyTaskCompletionSource CompletedTask
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get => _completed.IsValid ? _completed : _completed = new LazyTaskCompletionSource(LazyTaskState<Nothing>.CreateConstant(default));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A global LazyTaskCompletionSource that represents a cancelled operation
|
||||
/// </summary>
|
||||
public static LazyTaskCompletionSource CanceledTask
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get => _canceled.IsValid ? _canceled : _canceled = new LazyTaskCompletionSource(LazyTaskState<Nothing>.CreateCanceled());
|
||||
}
|
||||
|
||||
/// <summary><see cref="Object.Equals(Object)"/></summary>
|
||||
public override bool Equals(object? obj) => obj is LazyTaskCompletionSource ltcs && _state == ltcs._state && _token == ltcs._token;
|
||||
/// <summary><see cref="Object.GetHashCode"/></summary>
|
||||
public override int GetHashCode() => (_state == null ? 0 : _state.GetHashCode()) ^ _token;
|
||||
/// <summary><see cref="Object.ToString"/></summary>
|
||||
public override string ToString() => nameof(LazyTaskCompletionSource);
|
||||
|
||||
private readonly LazyTaskState<Nothing> _state;
|
||||
private readonly short _token;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the task associated with this instance
|
||||
/// </summary>
|
||||
public Task Task => _state?.GetTask(_token) ?? ThrowHelper.ThrowInvalidOperationException<Task>();
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private LazyTaskCompletionSource(LazyTaskState<Nothing> state)
|
||||
{
|
||||
_state = state;
|
||||
_token = state.Version;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a new instance; this instance should be disposed when it is known to be unwanted
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static LazyTaskCompletionSource Create()
|
||||
=> new LazyTaskCompletionSource(LazyTaskState<Nothing>.Create());
|
||||
|
||||
/// <summary>
|
||||
/// Set the result of the operation
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public bool TrySetResult() => _state?.TrySetResult(_token, default) == true;
|
||||
|
||||
/// <summary>
|
||||
/// Set the result of the operation
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public bool TrySetCanceled(CancellationToken cancellationToken = default) => _state?.TrySetCanceled(_token, cancellationToken) == true;
|
||||
|
||||
/// <summary>
|
||||
/// Set the result of the operation
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void SetResult()
|
||||
{
|
||||
if (!TrySetResult()) ThrowHelper.ThrowInvalidOperationException();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Set the result of the operation
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void SetCanceled(CancellationToken cancellationToken = default)
|
||||
{
|
||||
if (!TrySetCanceled(cancellationToken)) ThrowHelper.ThrowInvalidOperationException();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Set the result of the operation
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public bool TrySetException(Exception exception) => _state?.TrySetException(_token, exception) == true;
|
||||
|
||||
/// <summary>
|
||||
/// Set the result of the operation
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void SetException(Exception exception)
|
||||
{
|
||||
if (!TrySetException(exception)) ThrowHelper.ThrowInvalidOperationException();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Release all resources associated with this operation
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void Dispose() => _state?.Recycle(_token);
|
||||
|
||||
internal bool IsValid => _state?.IsValid(_token) == true;
|
||||
internal bool HasSource => _state?.HasSource == true;
|
||||
internal bool HasTask => _state?.HasTask == true;
|
||||
|
||||
/// <summary>
|
||||
/// Indicates whether this is an invalid default instance
|
||||
/// </summary>
|
||||
public bool IsNull
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get => _state == null;
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,126 @@
|
||||
using PooledAwait.Internal;
|
||||
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace PooledAwait
|
||||
{
|
||||
/// <summary>
|
||||
/// Like a ValueTaskCompletionSource<typeparamref name="T"/>, but the actual task will only become allocated
|
||||
/// if the .Task is consumed; this is useful for APIs where the consumer *may* consume a task
|
||||
/// </summary>
|
||||
public readonly struct LazyTaskCompletionSource<T> : IDisposable
|
||||
{
|
||||
/// <summary><see cref="Object.Equals(Object)"/></summary>
|
||||
public override bool Equals(object? obj) => obj is LazyTaskCompletionSource<T> ltcs && _state == ltcs._state && _token == ltcs._token;
|
||||
/// <summary><see cref="Object.GetHashCode"/></summary>
|
||||
public override int GetHashCode() => (_state == null ? 0 : _state.GetHashCode()) ^ _token;
|
||||
/// <summary><see cref="Object.ToString"/></summary>
|
||||
public override string ToString() => nameof(LazyTaskCompletionSource);
|
||||
|
||||
private readonly LazyTaskState<T> _state;
|
||||
private readonly short _token;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the task associated with this instance
|
||||
/// </summary>
|
||||
public Task<T> Task => (Task<T>)(_state?.GetTask(_token) ?? ThrowHelper.ThrowInvalidOperationException<Task<T>>());
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private LazyTaskCompletionSource(LazyTaskState<T> state)
|
||||
{
|
||||
_state = state;
|
||||
_token = state.Version;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a new instance; this instance should be disposed when it is known to be unwanted
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static LazyTaskCompletionSource<T> Create()
|
||||
=> new LazyTaskCompletionSource<T>(LazyTaskState<T>.Create());
|
||||
|
||||
/// <summary>
|
||||
/// Create a new instance; this instance will never by recycled
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static LazyTaskCompletionSource<T> CreateConstant(T value)
|
||||
=> new LazyTaskCompletionSource<T>(LazyTaskState<T>.CreateConstant(value));
|
||||
|
||||
|
||||
private static LazyTaskCompletionSource<T> _canceled;
|
||||
|
||||
/// <summary>
|
||||
/// A global LazyTaskCompletionSource that represents a cancelled operation
|
||||
/// </summary>
|
||||
public static LazyTaskCompletionSource<T> CanceledTask
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get => _canceled.IsValid ? _canceled : _canceled = new LazyTaskCompletionSource<T>(LazyTaskState<T>.CreateCanceled());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Set the result of the operation
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public bool TrySetResult(T result) => _state?.TrySetResult(_token, result) == true;
|
||||
|
||||
/// <summary>
|
||||
/// Set the result of the operation
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void SetResult(T result)
|
||||
{
|
||||
if (!TrySetResult(result)) ThrowHelper.ThrowInvalidOperationException();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Set the result of the operation
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public bool TrySetCanceled(CancellationToken cancellationToken = default)
|
||||
=> _state?.TrySetCanceled(_token, cancellationToken) == true;
|
||||
|
||||
/// <summary>
|
||||
/// Set the result of the operation
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void SetCanceled(CancellationToken cancellationToken = default)
|
||||
{
|
||||
if (!TrySetCanceled(cancellationToken)) ThrowHelper.ThrowInvalidOperationException();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Set the result of the operation
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public bool TrySetException(Exception exception) => _state?.TrySetException(_token, exception) == true;
|
||||
|
||||
/// <summary>
|
||||
/// Set the result of the operation
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void SetException(Exception exception)
|
||||
{
|
||||
if (!TrySetException(exception)) ThrowHelper.ThrowInvalidOperationException();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Release all resources associated with this operation
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void Dispose() => _state?.Recycle(_token);
|
||||
|
||||
internal bool IsValid => _state?.IsValid(_token) == true;
|
||||
internal bool HasSource => _state?.HasSource == true;
|
||||
internal bool HasTask => _state?.HasTask == true;
|
||||
|
||||
/// <summary>
|
||||
/// Indicates whether this is an invalid default instance
|
||||
/// </summary>
|
||||
public bool IsNull
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get => _state == null;
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,73 @@
|
||||
using PooledAwait.Internal;
|
||||
|
||||
using System.ComponentModel;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
#pragma warning disable CS1591
|
||||
|
||||
namespace PooledAwait.MethodBuilders
|
||||
{
|
||||
/// <summary>
|
||||
/// This type is not intended for direct usage
|
||||
/// </summary>
|
||||
[Browsable(false)]
|
||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||
public struct FireAndForgetMethodBuilder
|
||||
{
|
||||
public override bool Equals(object? obj) => ThrowHelper.ThrowNotSupportedException<bool>();
|
||||
public override int GetHashCode() => ThrowHelper.ThrowNotSupportedException<int>();
|
||||
public override string ToString() => nameof(FireAndForgetMethodBuilder);
|
||||
|
||||
[Browsable(false)]
|
||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static FireAndForgetMethodBuilder Create() => default;
|
||||
|
||||
[Browsable(false)]
|
||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void SetStateMachine(IAsyncStateMachine _) => Counters.SetStateMachine.Increment();
|
||||
|
||||
[Browsable(false)]
|
||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void SetResult() { }
|
||||
|
||||
[Browsable(false)]
|
||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void SetException(Exception exception) => FireAndForget.OnException(exception);
|
||||
|
||||
[Browsable(false)]
|
||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||
public FireAndForget Task
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get => default;
|
||||
}
|
||||
|
||||
[Browsable(false)]
|
||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void AwaitOnCompleted<TAwaiter, TStateMachine>(
|
||||
ref TAwaiter awaiter, ref TStateMachine stateMachine)
|
||||
where TAwaiter : INotifyCompletion
|
||||
where TStateMachine : IAsyncStateMachine
|
||||
=> StateMachineBox<TStateMachine>.AwaitOnCompleted(ref awaiter, ref stateMachine);
|
||||
|
||||
[Browsable(false)]
|
||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void AwaitUnsafeOnCompleted<TAwaiter, TStateMachine>(
|
||||
ref TAwaiter awaiter, ref TStateMachine stateMachine)
|
||||
where TAwaiter : ICriticalNotifyCompletion
|
||||
where TStateMachine : IAsyncStateMachine
|
||||
=> StateMachineBox<TStateMachine>.AwaitUnsafeOnCompleted(ref awaiter, ref stateMachine);
|
||||
|
||||
[Browsable(false)]
|
||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void Start<TStateMachine>(ref TStateMachine stateMachine)
|
||||
where TStateMachine : IAsyncStateMachine => stateMachine.MoveNext();
|
||||
}
|
||||
}
|
@@ -0,0 +1,105 @@
|
||||
using PooledAwait.Internal;
|
||||
|
||||
using System.ComponentModel;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
using SystemTask = System.Threading.Tasks.Task;
|
||||
|
||||
#pragma warning disable CS1591
|
||||
|
||||
namespace PooledAwait.MethodBuilders
|
||||
{
|
||||
/// <summary>
|
||||
/// This type is not intended for direct usage
|
||||
/// </summary>
|
||||
[Browsable(false)]
|
||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||
public struct PooledTaskMethodBuilder
|
||||
{
|
||||
public override bool Equals(object? obj) => ThrowHelper.ThrowNotSupportedException<bool>();
|
||||
public override int GetHashCode() => ThrowHelper.ThrowNotSupportedException<int>();
|
||||
public override string ToString() => nameof(PooledTaskMethodBuilder);
|
||||
|
||||
private ValueTaskCompletionSource<Nothing> _source;
|
||||
private Exception _exception;
|
||||
|
||||
[Browsable(false)]
|
||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static PooledTaskMethodBuilder Create() => default;
|
||||
|
||||
[Browsable(false)]
|
||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void SetStateMachine(IAsyncStateMachine _) => Counters.SetStateMachine.Increment();
|
||||
|
||||
[Browsable(false)]
|
||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void SetResult()
|
||||
{
|
||||
_source.TrySetResult(default);
|
||||
}
|
||||
|
||||
[Browsable(false)]
|
||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void SetException(Exception exception)
|
||||
{
|
||||
_source.TrySetException(exception);
|
||||
_exception = exception;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private void EnsureHasTask()
|
||||
{
|
||||
if (_source.IsNull) _source = ValueTaskCompletionSource<Nothing>.Create();
|
||||
}
|
||||
|
||||
[Browsable(false)]
|
||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||
public PooledTask Task
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get
|
||||
{
|
||||
SystemTask task;
|
||||
if (!_source.IsNull) task = _source.Task;
|
||||
else if (_exception is OperationCanceledException) task = TaskUtils.TaskFactory<Nothing>.Canceled;
|
||||
else if (_exception != null) task = TaskUtils.FromException(_exception);
|
||||
else task = TaskUtils.CompletedTask;
|
||||
return new PooledTask(task);
|
||||
}
|
||||
}
|
||||
|
||||
[Browsable(false)]
|
||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void AwaitOnCompleted<TAwaiter, TStateMachine>(
|
||||
ref TAwaiter awaiter, ref TStateMachine stateMachine)
|
||||
where TAwaiter : INotifyCompletion
|
||||
where TStateMachine : IAsyncStateMachine
|
||||
{
|
||||
EnsureHasTask();
|
||||
StateMachineBox<TStateMachine>.AwaitOnCompleted(ref awaiter, ref stateMachine);
|
||||
}
|
||||
|
||||
[Browsable(false)]
|
||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void AwaitUnsafeOnCompleted<TAwaiter, TStateMachine>(
|
||||
ref TAwaiter awaiter, ref TStateMachine stateMachine)
|
||||
where TAwaiter : ICriticalNotifyCompletion
|
||||
where TStateMachine : IAsyncStateMachine
|
||||
{
|
||||
EnsureHasTask();
|
||||
StateMachineBox<TStateMachine>.AwaitUnsafeOnCompleted(ref awaiter, ref stateMachine);
|
||||
}
|
||||
|
||||
[Browsable(false)]
|
||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void Start<TStateMachine>(ref TStateMachine stateMachine)
|
||||
where TStateMachine : IAsyncStateMachine => stateMachine.MoveNext();
|
||||
}
|
||||
}
|
@@ -0,0 +1,105 @@
|
||||
using PooledAwait.Internal;
|
||||
|
||||
using System.ComponentModel;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
#pragma warning disable CS1591
|
||||
|
||||
namespace PooledAwait.MethodBuilders
|
||||
{
|
||||
/// <summary>
|
||||
/// This type is not intended for direct usage
|
||||
/// </summary>
|
||||
[Browsable(false)]
|
||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||
public struct PooledTaskMethodBuilder<T>
|
||||
{
|
||||
public override bool Equals(object? obj) => ThrowHelper.ThrowNotSupportedException<bool>();
|
||||
public override int GetHashCode() => ThrowHelper.ThrowNotSupportedException<int>();
|
||||
public override string ToString() => nameof(PooledTaskMethodBuilder);
|
||||
|
||||
private ValueTaskCompletionSource<T> _source;
|
||||
private Exception _exception;
|
||||
private T _result;
|
||||
|
||||
[Browsable(false)]
|
||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static PooledTaskMethodBuilder<T> Create() => default;
|
||||
|
||||
[Browsable(false)]
|
||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void SetStateMachine(IAsyncStateMachine _) => Counters.SetStateMachine.Increment();
|
||||
|
||||
[Browsable(false)]
|
||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void SetResult(T result)
|
||||
{
|
||||
_source.TrySetResult(result);
|
||||
_result = result;
|
||||
}
|
||||
|
||||
[Browsable(false)]
|
||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void SetException(Exception exception)
|
||||
{
|
||||
_source.TrySetException(exception);
|
||||
_exception = exception;
|
||||
}
|
||||
|
||||
[Browsable(false)]
|
||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||
public PooledTask<T> Task
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get
|
||||
{
|
||||
Task<T> task;
|
||||
if (!_source.IsNull) task = _source.Task;
|
||||
else if (_exception is OperationCanceledException) task = TaskUtils.TaskFactory<T>.Canceled;
|
||||
else if (_exception != null) task = TaskUtils.FromException<T>(_exception);
|
||||
else task = TaskUtils.TaskFactory<T>.FromResult(_result);
|
||||
return new PooledTask<T>(task);
|
||||
}
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private void EnsureHasTask()
|
||||
{
|
||||
if (_source.IsNull) _source = ValueTaskCompletionSource<T>.Create();
|
||||
}
|
||||
|
||||
[Browsable(false)]
|
||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void AwaitOnCompleted<TAwaiter, TStateMachine>(
|
||||
ref TAwaiter awaiter, ref TStateMachine stateMachine)
|
||||
where TAwaiter : INotifyCompletion
|
||||
where TStateMachine : IAsyncStateMachine
|
||||
{
|
||||
EnsureHasTask();
|
||||
StateMachineBox<TStateMachine>.AwaitOnCompleted(ref awaiter, ref stateMachine);
|
||||
}
|
||||
|
||||
[Browsable(false)]
|
||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void AwaitUnsafeOnCompleted<TAwaiter, TStateMachine>(
|
||||
ref TAwaiter awaiter, ref TStateMachine stateMachine)
|
||||
where TAwaiter : ICriticalNotifyCompletion
|
||||
where TStateMachine : IAsyncStateMachine
|
||||
{
|
||||
EnsureHasTask();
|
||||
StateMachineBox<TStateMachine>.AwaitUnsafeOnCompleted(ref awaiter, ref stateMachine);
|
||||
}
|
||||
|
||||
[Browsable(false)]
|
||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void Start<TStateMachine>(ref TStateMachine stateMachine)
|
||||
where TStateMachine : IAsyncStateMachine => stateMachine.MoveNext();
|
||||
}
|
||||
}
|
@@ -0,0 +1,94 @@
|
||||
using PooledAwait.Internal;
|
||||
|
||||
using System.ComponentModel;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
#pragma warning disable CS1591
|
||||
|
||||
namespace PooledAwait.MethodBuilders
|
||||
{
|
||||
/// <summary>
|
||||
/// This type is not intended for direct usage
|
||||
/// </summary>
|
||||
[Browsable(false)]
|
||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||
public struct PooledValueTaskMethodBuilder
|
||||
{
|
||||
public override bool Equals(object? obj) => ThrowHelper.ThrowNotSupportedException<bool>();
|
||||
public override int GetHashCode() => ThrowHelper.ThrowNotSupportedException<int>();
|
||||
public override string ToString() => nameof(PooledValueTaskMethodBuilder);
|
||||
|
||||
private PooledValueTaskSource _source;
|
||||
|
||||
[Browsable(false)]
|
||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static PooledValueTaskMethodBuilder Create() => default;
|
||||
|
||||
[Browsable(false)]
|
||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void SetStateMachine(IAsyncStateMachine _) => Counters.SetStateMachine.Increment();
|
||||
|
||||
[Browsable(false)]
|
||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void SetResult()
|
||||
{
|
||||
_source.TrySetResult();
|
||||
}
|
||||
|
||||
[Browsable(false)]
|
||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void SetException(Exception exception)
|
||||
{
|
||||
EnsureHasTask();
|
||||
_source.TrySetException(exception);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private void EnsureHasTask()
|
||||
{
|
||||
if (!_source.HasTask) _source = PooledValueTaskSource.Create();
|
||||
}
|
||||
|
||||
[Browsable(false)]
|
||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||
public PooledValueTask Task
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get => _source.PooledTask;
|
||||
}
|
||||
|
||||
[Browsable(false)]
|
||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void AwaitOnCompleted<TAwaiter, TStateMachine>(
|
||||
ref TAwaiter awaiter, ref TStateMachine stateMachine)
|
||||
where TAwaiter : INotifyCompletion
|
||||
where TStateMachine : IAsyncStateMachine
|
||||
{
|
||||
EnsureHasTask();
|
||||
StateMachineBox<TStateMachine>.AwaitOnCompleted(ref awaiter, ref stateMachine);
|
||||
}
|
||||
|
||||
[Browsable(false)]
|
||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void AwaitUnsafeOnCompleted<TAwaiter, TStateMachine>(
|
||||
ref TAwaiter awaiter, ref TStateMachine stateMachine)
|
||||
where TAwaiter : ICriticalNotifyCompletion
|
||||
where TStateMachine : IAsyncStateMachine
|
||||
{
|
||||
EnsureHasTask();
|
||||
StateMachineBox<TStateMachine>.AwaitUnsafeOnCompleted(ref awaiter, ref stateMachine);
|
||||
}
|
||||
|
||||
[Browsable(false)]
|
||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void Start<TStateMachine>(ref TStateMachine stateMachine)
|
||||
where TStateMachine : IAsyncStateMachine => stateMachine.MoveNext();
|
||||
}
|
||||
}
|
@@ -0,0 +1,95 @@
|
||||
using PooledAwait.Internal;
|
||||
|
||||
using System.ComponentModel;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
#pragma warning disable CS1591
|
||||
|
||||
namespace PooledAwait.MethodBuilders
|
||||
{
|
||||
/// <summary>
|
||||
/// This type is not intended for direct usage
|
||||
/// </summary>
|
||||
[Browsable(false)]
|
||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||
public struct PooledValueTaskMethodBuilder<T>
|
||||
{
|
||||
public override bool Equals(object? obj) => ThrowHelper.ThrowNotSupportedException<bool>();
|
||||
public override int GetHashCode() => ThrowHelper.ThrowNotSupportedException<int>();
|
||||
public override string ToString() => nameof(PooledValueTaskMethodBuilder);
|
||||
|
||||
private PooledValueTaskSource<T> _source;
|
||||
|
||||
[Browsable(false)]
|
||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static PooledValueTaskMethodBuilder<T> Create() => default;
|
||||
|
||||
[Browsable(false)]
|
||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void SetStateMachine(IAsyncStateMachine _) => Counters.SetStateMachine.Increment();
|
||||
|
||||
[Browsable(false)]
|
||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void SetResult(T result)
|
||||
{
|
||||
if (_source.HasTask) _source.TrySetResult(result);
|
||||
else _source = new PooledValueTaskSource<T>(result);
|
||||
}
|
||||
|
||||
[Browsable(false)]
|
||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void SetException(Exception exception)
|
||||
{
|
||||
EnsureHasTask();
|
||||
_source.TrySetException(exception);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private void EnsureHasTask()
|
||||
{
|
||||
if (!_source.HasTask) _source = PooledValueTaskSource<T>.Create();
|
||||
}
|
||||
|
||||
[Browsable(false)]
|
||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||
public PooledValueTask<T> Task
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get => _source.PooledTask;
|
||||
}
|
||||
|
||||
[Browsable(false)]
|
||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void AwaitOnCompleted<TAwaiter, TStateMachine>(
|
||||
ref TAwaiter awaiter, ref TStateMachine stateMachine)
|
||||
where TAwaiter : INotifyCompletion
|
||||
where TStateMachine : IAsyncStateMachine
|
||||
{
|
||||
EnsureHasTask();
|
||||
StateMachineBox<TStateMachine>.AwaitOnCompleted(ref awaiter, ref stateMachine);
|
||||
}
|
||||
|
||||
[Browsable(false)]
|
||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void AwaitUnsafeOnCompleted<TAwaiter, TStateMachine>(
|
||||
ref TAwaiter awaiter, ref TStateMachine stateMachine)
|
||||
where TAwaiter : ICriticalNotifyCompletion
|
||||
where TStateMachine : IAsyncStateMachine
|
||||
{
|
||||
EnsureHasTask();
|
||||
StateMachineBox<TStateMachine>.AwaitUnsafeOnCompleted(ref awaiter, ref stateMachine);
|
||||
}
|
||||
|
||||
[Browsable(false)]
|
||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void Start<TStateMachine>(ref TStateMachine stateMachine)
|
||||
where TStateMachine : IAsyncStateMachine => stateMachine.MoveNext();
|
||||
}
|
||||
}
|
73
src/Admin/ThingsGateway.NewLife.X/PooledAwait/Pool.cs
Normal file
73
src/Admin/ThingsGateway.NewLife.X/PooledAwait/Pool.cs
Normal file
@@ -0,0 +1,73 @@
|
||||
using PooledAwait.Internal;
|
||||
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace PooledAwait
|
||||
{
|
||||
/// <summary>
|
||||
/// Utility methods for boxing value types efficiently, in particular for
|
||||
/// avoid boxes and capture contexts in callbacks
|
||||
/// </summary>
|
||||
public static class Pool
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets an instance from the pool if possible
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static T? TryRent<T>() where T : class
|
||||
=> Pool<T>.TryGet();
|
||||
|
||||
/// <summary>
|
||||
/// Puts an instance back into the pool
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void Return<T>(T value) where T : class
|
||||
{
|
||||
if (value is IResettable reset) reset.Reset();
|
||||
Pool<T>.TryPut(value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Wraps a value-type into a boxed instance, using an object pool;
|
||||
/// consider using value-tuples in particular
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static object Box<T>(in T value) where T : struct
|
||||
=> ItemBox<T>.Create(in value);
|
||||
|
||||
/// <summary>
|
||||
/// Unwraps a value-type from a boxed instance and recycles
|
||||
/// the instance, which should not be touched again
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static T UnboxAndReturn<T>(object obj) where T : struct
|
||||
=> ItemBox<T>.UnboxAndRecycle(obj);
|
||||
|
||||
internal sealed class ItemBox<T> where T : struct
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private ItemBox() => Counters.ItemBoxAllocated.Increment();
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static ItemBox<T> Create(in T value)
|
||||
{
|
||||
var box = Pool<ItemBox<T>>.TryGet() ?? new ItemBox<T>();
|
||||
box._value = value;
|
||||
return box;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static T UnboxAndRecycle(object obj)
|
||||
{
|
||||
var box = (ItemBox<T>)obj;
|
||||
var value = box._value;
|
||||
box._value = default;
|
||||
Pool<ItemBox<T>>.TryPut(box);
|
||||
return value;
|
||||
}
|
||||
#pragma warning disable IDE0044 // make field readonly? no, IDE, you're wrong
|
||||
private T _value;
|
||||
#pragma warning restore IDE0044
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,21 @@
|
||||
#if !NETSTANDARD1_3
|
||||
namespace PooledAwait
|
||||
{
|
||||
/// <summary>
|
||||
/// Controls the number of elements to store in the pool
|
||||
/// </summary>
|
||||
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct, AllowMultiple = false, Inherited = true)]
|
||||
public sealed class PoolSizeAttribute : Attribute
|
||||
{
|
||||
/// <summary>
|
||||
/// The number of elements to store in the pool
|
||||
/// </summary>
|
||||
public int Size { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Create a new PoolSizeAttribute instance
|
||||
/// </summary>
|
||||
public PoolSizeAttribute(int size) => Size = size;
|
||||
}
|
||||
}
|
||||
#endif
|
47
src/Admin/ThingsGateway.NewLife.X/PooledAwait/PoolT.cs
Normal file
47
src/Admin/ThingsGateway.NewLife.X/PooledAwait/PoolT.cs
Normal file
@@ -0,0 +1,47 @@
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
using ThingsGateway.NewLife.Collections;
|
||||
|
||||
namespace PooledAwait
|
||||
{
|
||||
/// <summary>
|
||||
/// A general-purpose pool of object references; it is the caller's responsibility
|
||||
/// to ensure that overlapped usage does not occur
|
||||
/// </summary>
|
||||
internal static class Pool<T> where T : class
|
||||
{
|
||||
private static ObjectPoolLock<T> pool = new();
|
||||
|
||||
[ThreadStatic]
|
||||
private static T? ts_local;
|
||||
|
||||
/// <summary>
|
||||
/// Gets an instance from the pool if possible
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static T? TryGet()
|
||||
{
|
||||
var tmp = ts_local;
|
||||
ts_local = null;
|
||||
return tmp ?? pool.Get();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Puts an instance back into the pool
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void TryPut(T value)
|
||||
{
|
||||
if (value != null)
|
||||
{
|
||||
if (ts_local == null)
|
||||
{
|
||||
ts_local = value;
|
||||
return;
|
||||
}
|
||||
pool.Return(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,29 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<Import Project="..\..\Foundation.props" />
|
||||
|
||||
<PropertyGroup>
|
||||
<IncludeAsyncInterfaces>false</IncludeAsyncInterfaces>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="$(TargetFramework)=='net6.0'">
|
||||
<DefineConstants>$(DefineConstants);PLAT_THREADPOOLWORKITEM;PLAT_MRVTSC</DefineConstants>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="$(TargetFramework)=='net8.0'">
|
||||
<DefineConstants>$(DefineConstants);PLAT_THREADPOOLWORKITEM;PLAT_MRVTSC</DefineConstants>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="$(TargetFramework)=='netstandard2.0'">
|
||||
<DefineConstants>$(DefineConstants);PLAT_MRVTSC</DefineConstants>
|
||||
<IncludeAsyncInterfaces>true</IncludeAsyncInterfaces>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="$(TargetFramework)=='net462'">
|
||||
<DefineConstants>$(DefineConstants);PLAT_MRVTSC</DefineConstants>
|
||||
<IncludeAsyncInterfaces>true</IncludeAsyncInterfaces>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.Bcl.AsyncInterfaces" Version="1.1.0" Condition="$(IncludeAsyncInterfaces)=='true'" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
60
src/Admin/ThingsGateway.NewLife.X/PooledAwait/PooledTask.cs
Normal file
60
src/Admin/ThingsGateway.NewLife.X/PooledAwait/PooledTask.cs
Normal file
@@ -0,0 +1,60 @@
|
||||
using PooledAwait.Internal;
|
||||
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace PooledAwait
|
||||
{
|
||||
/// <summary>
|
||||
/// A Task, but with a custom builder
|
||||
/// </summary>
|
||||
[AsyncMethodBuilder(typeof(MethodBuilders.PooledTaskMethodBuilder))]
|
||||
public readonly struct PooledTask
|
||||
{
|
||||
/// <summary><see cref="Object.Equals(Object)"/></summary>
|
||||
public override bool Equals(object? obj) => obj is PooledTask pt && _task == pt._task;
|
||||
/// <summary><see cref="Object.GetHashCode"/></summary>
|
||||
public override int GetHashCode() => _task == null ? 0 : _task.GetHashCode();
|
||||
/// <summary><see cref="Object.ToString"/></summary>
|
||||
public override string ToString() => nameof(PooledTask);
|
||||
|
||||
private readonly Task _task;
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
internal PooledTask(Task task) => _task = task;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the instance as a task
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public Task AsTask() => _task ?? TaskUtils.CompletedTask;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the instance as a task
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
|
||||
public static implicit operator Task(in PooledTask task) => task.AsTask();
|
||||
|
||||
/// <summary>
|
||||
/// Gets the awaiter for the task
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public TaskAwaiter GetAwaiter() => AsTask().GetAwaiter();
|
||||
|
||||
/// <summary>
|
||||
/// Gets the configured awaiter for the task
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public ConfiguredTaskAwaitable ConfigureAwait(bool continueOnCapturedContext)
|
||||
=> AsTask().ConfigureAwait(continueOnCapturedContext);
|
||||
|
||||
/// <summary>
|
||||
/// Indicates whether this is an invalid default instance
|
||||
/// </summary>
|
||||
public bool IsNull
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get => _task == null;
|
||||
}
|
||||
}
|
||||
}
|
60
src/Admin/ThingsGateway.NewLife.X/PooledAwait/PooledTaskT.cs
Normal file
60
src/Admin/ThingsGateway.NewLife.X/PooledAwait/PooledTaskT.cs
Normal file
@@ -0,0 +1,60 @@
|
||||
using PooledAwait.Internal;
|
||||
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace PooledAwait
|
||||
{
|
||||
/// <summary>
|
||||
/// A Task<typeparamref name="T"/>, but with a custom builder
|
||||
/// </summary>
|
||||
[AsyncMethodBuilder(typeof(MethodBuilders.PooledTaskMethodBuilder<>))]
|
||||
public readonly struct PooledTask<T>
|
||||
{
|
||||
/// <summary><see cref="Object.Equals(Object)"/></summary>
|
||||
public override bool Equals(object? obj) => obj is PooledTask<T> pt && _task == pt._task;
|
||||
/// <summary><see cref="Object.GetHashCode"/></summary>
|
||||
public override int GetHashCode() => _task == null ? 0 : _task.GetHashCode();
|
||||
/// <summary><see cref="Object.ToString"/></summary>
|
||||
public override string ToString() => nameof(PooledTask);
|
||||
|
||||
private readonly Task<T>? _task;
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
internal PooledTask(Task<T> task) => _task = task;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the instance as a task
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public Task<T> AsTask() => _task ?? ThrowHelper.ThrowInvalidOperationException<Task<T>>();
|
||||
|
||||
/// <summary>
|
||||
/// Gets the instance as a task
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
|
||||
public static implicit operator Task<T>(in PooledTask<T> task) => task.AsTask();
|
||||
|
||||
/// <summary>
|
||||
/// Gets the awaiter for the task
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public TaskAwaiter<T> GetAwaiter() => AsTask().GetAwaiter();
|
||||
|
||||
/// <summary>
|
||||
/// Gets the configured awaiter for the task
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public ConfiguredTaskAwaitable<T> ConfigureAwait(bool continueOnCapturedContext)
|
||||
=> AsTask().ConfigureAwait(continueOnCapturedContext);
|
||||
|
||||
/// <summary>
|
||||
/// Indicates whether this is an invalid default instance
|
||||
/// </summary>
|
||||
public bool IsNull
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get => _task == null;
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,71 @@
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Threading.Tasks.Sources;
|
||||
|
||||
namespace PooledAwait
|
||||
{
|
||||
/// <summary>
|
||||
/// A ValueTask with a custom source and builder
|
||||
/// </summary>
|
||||
[AsyncMethodBuilder(typeof(MethodBuilders.PooledValueTaskMethodBuilder))]
|
||||
public readonly struct PooledValueTask
|
||||
{
|
||||
/// <summary><see cref="Object.Equals(Object)"/></summary>
|
||||
public override bool Equals(object? obj) => obj is PooledValueTask pvt && _source == pvt._source && _token == pvt._token;
|
||||
/// <summary><see cref="Object.GetHashCode"/></summary>
|
||||
public override int GetHashCode() => (_source == null ? 0 : _source.GetHashCode()) ^ _token;
|
||||
/// <summary><see cref="Object.ToString"/></summary>
|
||||
public override string ToString() => nameof(PooledValueTask);
|
||||
|
||||
private readonly IValueTaskSource _source;
|
||||
private readonly short _token;
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
internal PooledValueTask(IValueTaskSource source, short token)
|
||||
{
|
||||
_source = source;
|
||||
_token = token;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the instance as a value-task
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public ValueTask AsValueTask() => _source == null ? default : new ValueTask(_source, _token);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the instance as a value-task
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static implicit operator ValueTask(in PooledValueTask task) => task.AsValueTask();
|
||||
|
||||
/// <summary>
|
||||
/// Gets the awaiter for the task
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
#pragma warning disable CA2012 // 正确使用 ValueTask
|
||||
public ValueTaskAwaiter GetAwaiter() => AsValueTask().GetAwaiter();
|
||||
#pragma warning restore CA2012 // 正确使用 ValueTask
|
||||
|
||||
/// <summary>
|
||||
/// Gets the configured awaiter for the task
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public ConfiguredValueTaskAwaitable ConfigureAwait(bool continueOnCapturedContext)
|
||||
=> AsValueTask().ConfigureAwait(continueOnCapturedContext);
|
||||
|
||||
/// <summary>
|
||||
/// Rents a task-source that will be recycled when the task is awaited
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
internal static PooledValueTaskSource CreateSource() => PooledValueTaskSource.Create();
|
||||
|
||||
/// <summary>
|
||||
/// Indicates whether this is an invalid default instance
|
||||
/// </summary>
|
||||
public bool IsNull
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get => _source == null;
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,126 @@
|
||||
using PooledAwait.Internal;
|
||||
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace PooledAwait
|
||||
{
|
||||
/// <summary>
|
||||
/// A task-source that automatically recycles when the task is awaited
|
||||
/// </summary>
|
||||
public readonly struct PooledValueTaskSource
|
||||
{
|
||||
/// <summary><see cref="Object.Equals(Object)"/></summary>
|
||||
public override bool Equals(object? obj) => obj is PooledValueTaskSource pvt && _source == pvt._source && _token == pvt._token;
|
||||
/// <summary><see cref="Object.GetHashCode"/></summary>
|
||||
public override int GetHashCode() => (_source == null ? 0 : _source.GetHashCode()) ^ _token;
|
||||
/// <summary><see cref="Object.ToString"/></summary>
|
||||
public override string ToString() => nameof(PooledValueTaskSource);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the task that corresponds to this instance; it can only be awaited once
|
||||
/// </summary>
|
||||
public ValueTask Task
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get => _source == null ? default : new ValueTask(_source, _token);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Indicates whether this instance is well-defined against a value task instance
|
||||
/// </summary>
|
||||
public bool HasTask
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get => _source != null;
|
||||
}
|
||||
|
||||
internal PooledValueTask PooledTask
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get => new PooledValueTask(_source, _token);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Rents a task-source that will be recycled when the task is awaited
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static PooledValueTaskSource Create()
|
||||
{
|
||||
var source = PooledState<Nothing>.Create(out var token);
|
||||
return new PooledValueTaskSource(source, token);
|
||||
}
|
||||
|
||||
private readonly PooledState<Nothing> _source;
|
||||
private readonly short _token;
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
internal PooledValueTaskSource(PooledState<Nothing> source, short token)
|
||||
{
|
||||
_source = source;
|
||||
_token = token;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Test whether the source is valid
|
||||
/// </summary>
|
||||
public bool IsValid
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get => _source?.IsValid(_token) == true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Set the result of the operation
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public bool TrySetResult() => _source?.TrySetResult(default, _token) == true;
|
||||
|
||||
/// <summary>
|
||||
/// Set the result of the operation
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void SetResult()
|
||||
{
|
||||
if (!TrySetResult()) ThrowHelper.ThrowInvalidOperationException();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Set the result of the operation
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public bool TrySetException(Exception error) => _source?.TrySetException(error, _token) == true;
|
||||
|
||||
/// <summary>
|
||||
/// Set the result of the operation
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void SetException(Exception exception)
|
||||
{
|
||||
if (!TrySetException(exception)) ThrowHelper.ThrowInvalidOperationException();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Set the result of the operation
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public bool TrySetCanceled() => _source?.TrySetCanceled(_token) == true;
|
||||
|
||||
/// <summary>
|
||||
/// Set the result of the operation
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void SetCanceled()
|
||||
{
|
||||
if (!TrySetCanceled()) ThrowHelper.ThrowInvalidOperationException();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Indicates whether this is an invalid default instance
|
||||
/// </summary>
|
||||
public bool IsNull
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get => _source == null;
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,141 @@
|
||||
using PooledAwait.Internal;
|
||||
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace PooledAwait
|
||||
{
|
||||
/// <summary>
|
||||
/// A task-source that automatically recycles when the task is awaited
|
||||
/// </summary>
|
||||
public readonly struct PooledValueTaskSource<T>
|
||||
{
|
||||
/// <summary><see cref="Object.Equals(Object)"/></summary>
|
||||
public override bool Equals(object? obj) => obj is PooledValueTaskSource<T> pvt && _token == pvt._token &&
|
||||
(_source != null ? _source == pvt._source : (pvt._source == null && EqualityComparer<T>.Default.Equals(_value, pvt._value)));
|
||||
/// <summary><see cref="Object.GetHashCode"/></summary>
|
||||
public override int GetHashCode() => (_source == null ? EqualityComparer<T>.Default.GetHashCode(_value!) : _source.GetHashCode()) ^ _token;
|
||||
/// <summary><see cref="Object.ToString"/></summary>
|
||||
public override string ToString() => nameof(PooledValueTaskSource);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the task that corresponds to this instance; it can only be awaited once
|
||||
/// </summary>
|
||||
public ValueTask<T> Task
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get => _source == null ? new ValueTask<T>(_value) : new ValueTask<T>(_source, _token);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Indicates whether this instance is well-defined against a value task instance
|
||||
/// </summary>
|
||||
public bool HasTask
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get => _source != null;
|
||||
}
|
||||
|
||||
internal PooledValueTask<T> PooledTask
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get => _source == null ? new PooledValueTask<T>(_value) : new PooledValueTask<T>(_source, _token);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Rents a task-source that will be recycled when the task is awaited
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static PooledValueTaskSource<T> Create()
|
||||
{
|
||||
var source = PooledState<T>.Create(out var token);
|
||||
return new PooledValueTaskSource<T>(source, token);
|
||||
}
|
||||
|
||||
private readonly PooledState<T>? _source;
|
||||
private readonly short _token;
|
||||
|
||||
private readonly T _value;
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
internal PooledValueTaskSource(PooledState<T> source, short token)
|
||||
{
|
||||
_source = source;
|
||||
_token = token;
|
||||
_value = default!;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a new PooledValueTaskSource that will yield a constant value without ever renting/recycling any background state
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public PooledValueTaskSource(T value)
|
||||
{
|
||||
_source = null;
|
||||
_token = default;
|
||||
_value = value!;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Test whether the source is valid
|
||||
/// </summary>
|
||||
public bool IsValid
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get => _source?.IsValid(_token) == true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Set the result of the operation
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public bool TrySetResult(T result) => _source?.TrySetResult(result, _token) == true;
|
||||
|
||||
/// <summary>
|
||||
/// Set the result of the operation
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void SetResult(T result)
|
||||
{
|
||||
if (!TrySetResult(result)) ThrowHelper.ThrowInvalidOperationException();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Set the result of the operation
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public bool TrySetException(Exception error) => _source?.TrySetException(error, _token) == true;
|
||||
|
||||
/// <summary>
|
||||
/// Set the result of the operation
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void SetException(Exception exception)
|
||||
{
|
||||
if (!TrySetException(exception)) ThrowHelper.ThrowInvalidOperationException();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Set the result of the operation
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public bool TrySetCanceled() => _source?.TrySetCanceled(_token) == true;
|
||||
|
||||
/// <summary>
|
||||
/// Set the result of the operation
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void SetCanceled()
|
||||
{
|
||||
if (!TrySetCanceled()) ThrowHelper.ThrowInvalidOperationException();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Indicates whether this is an invalid default instance
|
||||
/// </summary>
|
||||
public bool IsNull
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get => _source == null;
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,85 @@
|
||||
using PooledAwait.Internal;
|
||||
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace PooledAwait
|
||||
{
|
||||
/// <summary>
|
||||
/// A ValueTask<typeparamref name="T"/> with a custom source and builder
|
||||
/// </summary>
|
||||
[AsyncMethodBuilder(typeof(MethodBuilders.PooledValueTaskMethodBuilder<>))]
|
||||
public readonly struct PooledValueTask<T>
|
||||
{
|
||||
/// <summary><see cref="Object.Equals(Object)"/></summary>
|
||||
public override bool Equals(object? obj) => obj is PooledValueTask<T> pvt && _source == pvt._source && _token == pvt._token;
|
||||
/// <summary><see cref="Object.GetHashCode"/></summary>
|
||||
public override int GetHashCode() => (_source == null ? 0 : _source.GetHashCode()) ^ _token;
|
||||
/// <summary><see cref="Object.ToString"/></summary>
|
||||
public override string ToString() => nameof(PooledValueTask);
|
||||
|
||||
private readonly PooledState<T>? _source;
|
||||
private readonly short _token;
|
||||
private readonly T _result;
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
internal PooledValueTask(PooledState<T> source, short token)
|
||||
{
|
||||
_source = source;
|
||||
_token = token;
|
||||
_result = default!;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a value-task with a fixed value
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public PooledValueTask(T result)
|
||||
{
|
||||
_source = default;
|
||||
_token = default;
|
||||
_result = result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the instance as a value-task
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public ValueTask<T> AsValueTask() => _source == null ? new ValueTask<T>(_result) : new ValueTask<T>(_source, _token);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the instance as a value-task
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static implicit operator ValueTask<T>(in PooledValueTask<T> task) => task.AsValueTask();
|
||||
|
||||
/// <summary>
|
||||
/// Gets the awaiter for the task
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
#pragma warning disable CA2012 // 正确使用 ValueTask
|
||||
public ValueTaskAwaiter<T> GetAwaiter() => AsValueTask().GetAwaiter();
|
||||
#pragma warning restore CA2012 // 正确使用 ValueTask
|
||||
|
||||
/// <summary>
|
||||
/// Gets the configured awaiter for the task
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public ConfiguredValueTaskAwaitable<T> ConfigureAwait(bool continueOnCapturedContext)
|
||||
=> AsValueTask().ConfigureAwait(continueOnCapturedContext);
|
||||
|
||||
/// <summary>
|
||||
/// Rents a task-source that will be recycled when the task is awaited
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
internal static PooledValueTaskSource<T> CreateSource() => PooledValueTaskSource<T>.Create();
|
||||
|
||||
/// <summary>
|
||||
/// Indicates whether this is an invalid default instance
|
||||
/// </summary>
|
||||
public bool IsNull
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get => _source == null;
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,5 @@
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
[assembly: InternalsVisibleTo("Benchmark, PublicKey=0024000004800000940000000602000000240000525341310004000001000100d136a87a0c11ded39332f7ead1f2ce53256a42a350ee847df1dc520bc781210a5f1fe73b3f91a4001fdcfa35340a212e70443de46e6928510f57a55f4ab9b95257f5067d4de4be8cdad460781866b20a6ca20f66302df61b52e55e16c080cad95aae8b94ffdd54718b9963d0240b0d0d5afe655b05c91771f7dc8a314387d3c3")]
|
||||
|
||||
[assembly: InternalsVisibleTo("PooledAwait.Test, PublicKey=0024000004800000940000000602000000240000525341310004000001000100d136a87a0c11ded39332f7ead1f2ce53256a42a350ee847df1dc520bc781210a5f1fe73b3f91a4001fdcfa35340a212e70443de46e6928510f57a55f4ab9b95257f5067d4de4be8cdad460781866b20a6ca20f66302df61b52e55e16c080cad95aae8b94ffdd54718b9963d0240b0d0d5afe655b05c91771f7dc8a314387d3c3")]
|
@@ -0,0 +1,218 @@
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
using PooledAwait.Internal;
|
||||
|
||||
#if !NETSTANDARD1_3
|
||||
using System.Reflection;
|
||||
#endif
|
||||
|
||||
namespace PooledAwait
|
||||
{
|
||||
/// <summary>
|
||||
/// Lightweight implementation of TaskCompletionSource<typeparamref name="T"/>
|
||||
/// </summary>
|
||||
/// <remarks>When possible, this will bypass TaskCompletionSource<typeparamref name="T"/> completely</remarks>
|
||||
public readonly struct ValueTaskCompletionSource<T>
|
||||
{
|
||||
/// <summary><see cref="Object.Equals(Object)"/></summary>
|
||||
public override bool Equals(object? obj) => obj is ValueTaskCompletionSource<T> other && _state == other._state;
|
||||
/// <summary><see cref="Object.GetHashCode"/></summary>
|
||||
public override int GetHashCode() => _state == null ? 0 : _state.GetHashCode();
|
||||
/// <summary><see cref="Object.ToString"/></summary>
|
||||
public override string ToString() => "ValueTaskCompletionSource";
|
||||
|
||||
#if !NETSTANDARD1_3
|
||||
private static readonly Func<Task<T>, Exception, bool>? s_TrySetException = TryCreate<Exception>(nameof(TrySetException));
|
||||
private static readonly Func<Task<T>, T, bool>? s_TrySetResult = TryCreate<T>(nameof(TrySetResult));
|
||||
private static readonly Func<Task<T>, CancellationToken, bool>? s_TrySetCanceled = TryCreate<CancellationToken>(nameof(TrySetCanceled));
|
||||
private static readonly bool s_Optimized = ValidateOptimized();
|
||||
#endif
|
||||
private readonly object _state;
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private ValueTaskCompletionSource(object state) => _state = state;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the instance as a task
|
||||
/// </summary>
|
||||
public Task<T> Task
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get => _state as Task<T> ?? ((TaskCompletionSource<T>)_state).Task;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Indicates whether this is an invalid default instance
|
||||
/// </summary>
|
||||
public bool IsNull
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get => _state == null;
|
||||
}
|
||||
|
||||
internal bool IsOptimized => _state is Task<T>;
|
||||
|
||||
/// <summary>
|
||||
/// Create an instance pointing to a new task
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static ValueTaskCompletionSource<T> Create() =>
|
||||
#if !NETSTANDARD1_3
|
||||
s_Optimized ? CreateOptimized() :
|
||||
#endif
|
||||
CreateFallback();
|
||||
|
||||
#if !NETSTANDARD1_3
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
internal static ValueTaskCompletionSource<T> CreateOptimized()
|
||||
{
|
||||
Counters.TaskAllocated.Increment();
|
||||
return new ValueTaskCompletionSource<T>(new TaskCompletionSource<T>(TaskCreationOptions.RunContinuationsAsynchronously));
|
||||
}
|
||||
#endif
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
internal static ValueTaskCompletionSource<T> CreateFallback()
|
||||
{
|
||||
Counters.TaskAllocated.Increment();
|
||||
return new ValueTaskCompletionSource<T>(new TaskCompletionSource<T>());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Set the outcome of the operation
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public bool TrySetException(Exception exception)
|
||||
{
|
||||
#if !NETSTANDARD1_3
|
||||
if (_state is Task<T> task)
|
||||
{
|
||||
var result = s_TrySetException!(task, exception);
|
||||
if (!result && !task.IsCompleted) SpinUntilCompleted(task);
|
||||
return result;
|
||||
}
|
||||
#endif
|
||||
return _state != null && ((TaskCompletionSource<T>)_state).TrySetException(exception);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Set the result of the operation
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void SetException(Exception exception)
|
||||
{
|
||||
if (!TrySetException(exception)) ThrowHelper.ThrowInvalidOperationException();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Set the outcome of the operation
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public bool TrySetCanceled(CancellationToken cancellationToken = default)
|
||||
{
|
||||
#if !NETSTANDARD1_3
|
||||
if (_state is Task<T> task)
|
||||
{
|
||||
var result = s_TrySetCanceled!(task, cancellationToken);
|
||||
if (!result && !task.IsCompleted) SpinUntilCompleted(task);
|
||||
return result;
|
||||
}
|
||||
#endif
|
||||
return _state != null && ((TaskCompletionSource<T>)_state).TrySetCanceled(
|
||||
#if !NET45
|
||||
cancellationToken
|
||||
#endif
|
||||
);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Set the result of the operation
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void SetCanceled(CancellationToken cancellationToken = default)
|
||||
{
|
||||
if (!TrySetCanceled(cancellationToken)) ThrowHelper.ThrowInvalidOperationException();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Set the outcome of the operation
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public bool TrySetResult(T value)
|
||||
{
|
||||
#if !NETSTANDARD1_3
|
||||
if (_state is Task<T> task)
|
||||
{
|
||||
var result = s_TrySetResult!(task, value);
|
||||
if (!result && !task.IsCompleted) SpinUntilCompleted(task);
|
||||
return result;
|
||||
}
|
||||
#endif
|
||||
return _state != null && ((TaskCompletionSource<T>)_state).TrySetResult(value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Set the result of the operation
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void SetResult(T value)
|
||||
{
|
||||
if (!TrySetResult(value)) ThrowHelper.ThrowInvalidOperationException();
|
||||
}
|
||||
|
||||
#if !NETSTANDARD1_3
|
||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||
private static Func<Task<T>, TArg, bool>? TryCreate<TArg>(string methodName)
|
||||
{
|
||||
try
|
||||
{
|
||||
return (Func<Task<T>, TArg, bool>)Delegate.CreateDelegate(
|
||||
typeof(Func<Task<T>, TArg, bool>),
|
||||
typeof(Task<T>).GetMethod(methodName, BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance,
|
||||
null, new[] { typeof(TArg) }, null)!);
|
||||
}
|
||||
catch { return null; }
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||
private static bool ValidateOptimized()
|
||||
{
|
||||
try
|
||||
{
|
||||
// perform feature tests of our voodoo
|
||||
var source = CreateOptimized();
|
||||
var task = source.Task;
|
||||
if (task == null) return false;
|
||||
if (task.IsCompleted) return false;
|
||||
|
||||
if (!source.TrySetResult(default!)) return false;
|
||||
if (task.Status != TaskStatus.RanToCompletion) return false;
|
||||
|
||||
source = CreateOptimized();
|
||||
task = source.Task;
|
||||
if (!source.TrySetException(new InvalidOperationException())) return false;
|
||||
if (!task.IsCompleted) return false;
|
||||
if (!task.IsFaulted) return false;
|
||||
try
|
||||
{
|
||||
_ = task.Result;
|
||||
return false;
|
||||
}
|
||||
catch (AggregateException ex) when (ex.InnerException is InvalidOperationException) { }
|
||||
if (!(task.Exception?.InnerException is InvalidOperationException)) return false;
|
||||
return true;
|
||||
}
|
||||
catch { return false; }
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||
private void SpinUntilCompleted(Task<T> task)
|
||||
{
|
||||
// Spin wait until the completion is finalized by another thread.
|
||||
var sw = new SpinWait();
|
||||
while (!task.IsCompleted)
|
||||
sw.SpinOnce();
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
@@ -24,7 +24,7 @@
|
||||
<None Remove="..\..\..\README.zh-CN.md" Pack="false" PackagePath="\" />
|
||||
</ItemGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(TargetFramework)'=='net462' or '$(TargetFramework)'=='net5.0-windows' or '$(TargetFramework)'=='net6.0-windows' or '$(TargetFramework)'=='net7.0-windows' or '$(TargetFramework)'=='net8.0-windows'">
|
||||
<PropertyGroup Condition="'$(TargetFramework)'=='net47' or '$(TargetFramework)'=='net5.0-windows' or '$(TargetFramework)'=='net6.0-windows' or '$(TargetFramework)'=='net7.0-windows' or '$(TargetFramework)'=='net8.0-windows'">
|
||||
<DefineConstants>__WIN__</DefineConstants>
|
||||
</PropertyGroup>
|
||||
|
||||
@@ -54,7 +54,26 @@
|
||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.4" />
|
||||
</ItemGroup>
|
||||
|
||||
<PropertyGroup Condition="$(TargetFramework)=='net6.0'">
|
||||
<DefineConstants>$(DefineConstants);PLAT_THREADPOOLWORKITEM;PLAT_MRVTSC</DefineConstants>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="$(TargetFramework)=='net8.0'">
|
||||
<DefineConstants>$(DefineConstants);PLAT_THREADPOOLWORKITEM;PLAT_MRVTSC</DefineConstants>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="$(TargetFramework)=='netstandard2.0'">
|
||||
<DefineConstants>$(DefineConstants);PLAT_MRVTSC</DefineConstants>
|
||||
<IncludeAsyncInterfaces>true</IncludeAsyncInterfaces>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="$(TargetFramework)=='net47'">
|
||||
<DefineConstants>$(DefineConstants);PLAT_MRVTSC</DefineConstants>
|
||||
<IncludeAsyncInterfaces>true</IncludeAsyncInterfaces>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.Bcl.AsyncInterfaces" Version="9.0.10" Condition="$(IncludeAsyncInterfaces)=='true'" />
|
||||
</ItemGroup>
|
||||
|
||||
<!--<ItemGroup Condition="'$(TargetFramework)'=='net6.0-windows' or '$(TargetFramework)'=='net6.0' ">
|
||||
<PackageReference Include="Microsoft.AspNetCore.DataProtection" Version="6.0.36" />
|
||||
|
@@ -1,4 +1,4 @@
|
||||
using System.Diagnostics;
|
||||
using PooledAwait;
|
||||
|
||||
using ThingsGateway.NewLife.Collections;
|
||||
using ThingsGateway.NewLife.Log;
|
||||
@@ -244,7 +244,7 @@ public class TimerScheduler : IDisposable, ILogFeature
|
||||
// 必须在主线程设置状态,否则可能异步线程还没来得及设置开始状态,主线程又开始了新的一轮调度
|
||||
timer.Calling = true;
|
||||
if (timer.IsAsyncTask)
|
||||
ExecuteAsync(timer);
|
||||
_ = ExecuteAsync(timer);
|
||||
//Task.Factory.StartNew(ExecuteAsync, timer, CancellationToken.None, TaskCreationOptions.None, TaskScheduler.Default);
|
||||
else if (!timer.Async)
|
||||
Execute(timer);
|
||||
@@ -321,8 +321,7 @@ public class TimerScheduler : IDisposable, ILogFeature
|
||||
string tracerName = timer.TracerName ?? "timer:ExecuteAsync";
|
||||
string timerArg = timer.Timers.ToString();
|
||||
using var span = timer.Tracer?.NewSpan(tracerName, timerArg);
|
||||
var sw = _stopwatchPool.Get();
|
||||
sw.Restart();
|
||||
var sw = ValueStopwatch.StartNew();
|
||||
try
|
||||
{
|
||||
// 弱引用判断
|
||||
@@ -350,92 +349,89 @@ public class TimerScheduler : IDisposable, ILogFeature
|
||||
}
|
||||
finally
|
||||
{
|
||||
sw.Stop();
|
||||
|
||||
OnExecuted(timer, (Int32)sw.ElapsedMilliseconds);
|
||||
|
||||
_stopwatchPool.Return(sw);
|
||||
var ms = sw.GetElapsedTime().TotalMilliseconds;
|
||||
OnExecuted(timer, (Int32)ms);
|
||||
}
|
||||
}
|
||||
private static ObjectPool<Stopwatch> _stopwatchPool { get; } = new ObjectPool<Stopwatch>();
|
||||
|
||||
/// <summary>处理每一个定时器</summary>
|
||||
/// <param name="state"></param>
|
||||
private async void ExecuteAsync(Object? state)
|
||||
private Task ExecuteAsync(Object? state)
|
||||
{
|
||||
if (state is not TimerX timer) return;
|
||||
|
||||
//TimerX.Current = timer;
|
||||
|
||||
// 控制日志显示
|
||||
//WriteLogEventArgs.CurrentThreadName = Name == "Default" ? "T" : Name;
|
||||
|
||||
timer.hasSetNext = false;
|
||||
|
||||
string tracerName = timer.TracerName ?? "timer:ExecuteAsync";
|
||||
string timerArg = timer.Timers.ToString();
|
||||
using var span = timer.Tracer?.NewSpan(tracerName, timerArg);
|
||||
var sw = _stopwatchPool.Get();
|
||||
sw.Restart();
|
||||
try
|
||||
return ExecuteAsync(this, state);
|
||||
static async PooledTask ExecuteAsync(TimerScheduler @this, Object? state)
|
||||
{
|
||||
// 弱引用判断
|
||||
var target = timer.Target.Target;
|
||||
if (target == null && !timer.Method.IsStatic)
|
||||
if (state is not TimerX timer) return;
|
||||
|
||||
//TimerX.Current = timer;
|
||||
|
||||
// 控制日志显示
|
||||
//WriteLogEventArgs.CurrentThreadName = Name == "Default" ? "T" : Name;
|
||||
|
||||
timer.hasSetNext = false;
|
||||
|
||||
string tracerName = timer.TracerName ?? "timer:ExecuteAsync";
|
||||
string timerArg = timer.Timers.ToString();
|
||||
using var span = timer.Tracer?.NewSpan(tracerName, timerArg);
|
||||
var sw = ValueStopwatch.StartNew();
|
||||
try
|
||||
{
|
||||
Remove(timer, "委托已不存在(GC回收委托所在对象)");
|
||||
timer.Dispose();
|
||||
return;
|
||||
}
|
||||
// 弱引用判断
|
||||
var target = timer.Target.Target;
|
||||
if (target == null && !timer.Method.IsStatic)
|
||||
{
|
||||
@this.Remove(timer, "委托已不存在(GC回收委托所在对象)");
|
||||
timer.Dispose();
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
|
||||
#if NET6_0_OR_GREATER
|
||||
if (timer.IsValueTask)
|
||||
{
|
||||
if (timer.ValueTaskCachedDelegate == null)
|
||||
if (timer.IsValueTask)
|
||||
{
|
||||
timer.ValueTaskCachedDelegate = timer.Method.As<Func<Object?, ValueTask>>(target);
|
||||
}
|
||||
if (timer.ValueTaskCachedDelegate == null)
|
||||
{
|
||||
timer.ValueTaskCachedDelegate = timer.Method.As<Func<Object?, ValueTask>>(target);
|
||||
}
|
||||
|
||||
//var func = timer.Method.As<Func<Object?, ValueTask>>(target);
|
||||
var task = timer.ValueTaskCachedDelegate!(timer.State);
|
||||
if (!task.IsCompleted)
|
||||
await task.ConfigureAwait(false);
|
||||
}
|
||||
else
|
||||
//var func = timer.Method.As<Func<Object?, ValueTask>>(target);
|
||||
var task = timer.ValueTaskCachedDelegate!(timer.State);
|
||||
if (!task.IsCompleted)
|
||||
await task.ConfigureAwait(false);
|
||||
}
|
||||
else
|
||||
#endif
|
||||
{
|
||||
if (timer.TaskCachedDelegate == null)
|
||||
{
|
||||
timer.TaskCachedDelegate = timer.Method.As<Func<Object?, Task>>(target);
|
||||
if (timer.TaskCachedDelegate == null)
|
||||
{
|
||||
timer.TaskCachedDelegate = timer.Method.As<Func<Object?, Task>>(target);
|
||||
}
|
||||
|
||||
//var func = timer.Method.As<Func<Object?, Task>>(target);
|
||||
var task = timer.TaskCachedDelegate!(timer.State);
|
||||
if (!task.IsCompleted)
|
||||
await task.ConfigureAwait(false);
|
||||
}
|
||||
|
||||
//var func = timer.Method.As<Func<Object?, Task>>(target);
|
||||
var task = timer.TaskCachedDelegate!(timer.State);
|
||||
if (!task.IsCompleted)
|
||||
await task.ConfigureAwait(false);
|
||||
}
|
||||
catch (ThreadAbortException) { throw; }
|
||||
catch (ThreadInterruptedException) { throw; }
|
||||
// 如果用户代码没有拦截错误,则这里拦截,避免出错了都不知道怎么回事
|
||||
catch (Exception ex)
|
||||
{
|
||||
span?.SetError(ex, null);
|
||||
XTrace.WriteException(ex);
|
||||
}
|
||||
finally
|
||||
{
|
||||
var ms = sw.GetElapsedTime().TotalMilliseconds;
|
||||
|
||||
}
|
||||
catch (ThreadAbortException) { throw; }
|
||||
catch (ThreadInterruptedException) { throw; }
|
||||
// 如果用户代码没有拦截错误,则这里拦截,避免出错了都不知道怎么回事
|
||||
catch (Exception ex)
|
||||
{
|
||||
span?.SetError(ex, null);
|
||||
XTrace.WriteException(ex);
|
||||
}
|
||||
finally
|
||||
{
|
||||
sw.Stop();
|
||||
@this.OnExecuted(timer, (Int32)ms);
|
||||
|
||||
OnExecuted(timer, (Int32)sw.ElapsedMilliseconds);
|
||||
|
||||
_stopwatchPool.Return(sw);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void OnExecuted(TimerX timer, Int32 ms)
|
||||
{
|
||||
timer.Cost = timer.Cost == 0 ? ms : (timer.Cost + ms) / 2;
|
||||
|
@@ -1,9 +1,9 @@
|
||||
<Project>
|
||||
|
||||
<PropertyGroup>
|
||||
<PluginVersion>10.11.110</PluginVersion>
|
||||
<ProPluginVersion>10.11.110</ProPluginVersion>
|
||||
<DefaultVersion>10.11.110</DefaultVersion>
|
||||
<PluginVersion>10.11.111</PluginVersion>
|
||||
<ProPluginVersion>10.11.111</ProPluginVersion>
|
||||
<DefaultVersion>10.11.111</DefaultVersion>
|
||||
<AuthenticationVersion>10.11.6</AuthenticationVersion>
|
||||
<SourceGeneratorVersion>10.11.6</SourceGeneratorVersion>
|
||||
<NET8Version>8.0.21</NET8Version>
|
||||
@@ -12,7 +12,7 @@
|
||||
<IsTrimmable>false</IsTrimmable>
|
||||
<ManagementProPluginVersion>10.11.87</ManagementProPluginVersion>
|
||||
<ManagementPluginVersion>10.11.87</ManagementPluginVersion>
|
||||
<TSVersion>4.0.0-beta.130</TSVersion>
|
||||
<TSVersion>4.0.0-beta.135</TSVersion>
|
||||
|
||||
|
||||
</PropertyGroup>
|
||||
|
@@ -34,25 +34,29 @@ public static class PluginUtil
|
||||
|
||||
if (channelOptions.ChannelType == ChannelTypeEnum.TcpClient)
|
||||
{
|
||||
action += a => a.UseReconnection<IClientChannel>().SetActionForCheck((channel, failCount) =>
|
||||
action += a => a.UseReconnection<IClientChannel>(a =>
|
||||
{
|
||||
if (channel.Online)
|
||||
a.CheckAction = (channel, failCount) =>
|
||||
{
|
||||
return Task.FromResult(ConnectionCheckResult.Alive);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (failCount > 1)
|
||||
if (channel.Online)
|
||||
{
|
||||
return Task.FromResult(ConnectionCheckResult.Dead);
|
||||
return Task.FromResult(ConnectionCheckResult.Alive);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (failCount > 1)
|
||||
{
|
||||
return Task.FromResult(ConnectionCheckResult.Dead);
|
||||
}
|
||||
return Task.FromResult(ConnectionCheckResult.Skip);
|
||||
}
|
||||
return Task.FromResult(ConnectionCheckResult.Skip);
|
||||
}
|
||||
|
||||
|
||||
})
|
||||
.SetPollingTick(TimeSpan.FromSeconds(5)
|
||||
};
|
||||
a.PollingInterval = TimeSpan.FromSeconds(5);
|
||||
|
||||
|
||||
}
|
||||
);
|
||||
}
|
||||
return action;
|
||||
|
@@ -10,6 +10,8 @@
|
||||
|
||||
using Newtonsoft.Json.Linq;
|
||||
|
||||
using PooledAwait;
|
||||
|
||||
using System.Net;
|
||||
|
||||
using ThingsGateway.Foundation.Extension.Generic;
|
||||
@@ -496,16 +498,21 @@ public abstract class DeviceBase : AsyncAndSyncDisposableObject, IDevice
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public virtual async ValueTask<OperResult<ReadOnlyMemory<byte>>> SendThenReturnAsync(ISendMessage sendMessage, IClientChannel channel, CancellationToken cancellationToken = default)
|
||||
public virtual ValueTask<OperResult<ReadOnlyMemory<byte>>> SendThenReturnAsync(ISendMessage sendMessage, IClientChannel channel, CancellationToken cancellationToken = default)
|
||||
{
|
||||
try
|
||||
return SendThenReturn(this, sendMessage, channel, cancellationToken);
|
||||
|
||||
static async PooledValueTask<OperResult<ReadOnlyMemory<byte>>> SendThenReturn(DeviceBase @this, ISendMessage sendMessage, IClientChannel channel, CancellationToken cancellationToken)
|
||||
{
|
||||
var result = await SendThenReturnMessageAsync(sendMessage, channel, cancellationToken).ConfigureAwait(false);
|
||||
return new OperResult<ReadOnlyMemory<byte>>(result) { Content = result.Content };
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return new(ex);
|
||||
try
|
||||
{
|
||||
var result = await @this.SendThenReturnMessageAsync(sendMessage, channel, cancellationToken).ConfigureAwait(false);
|
||||
return new OperResult<ReadOnlyMemory<byte>>(result) { Content = result.Content };
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return new(ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -523,48 +530,83 @@ public abstract class DeviceBase : AsyncAndSyncDisposableObject, IDevice
|
||||
return GetResponsedDataAsync(command, clientChannel, Timeout, cancellationToken);
|
||||
}
|
||||
|
||||
private ObjectPool<ReusableCancellationTokenSource> _reusableTimeouts = new();
|
||||
private ObjectPoolLock<ReusableCancellationTokenSource> _reusableTimeouts = new();
|
||||
|
||||
/// <summary>
|
||||
/// 发送并等待数据
|
||||
/// </summary>
|
||||
protected async ValueTask<MessageBase> GetResponsedDataAsync(
|
||||
protected ValueTask<MessageBase> GetResponsedDataAsync(
|
||||
ISendMessage command,
|
||||
IClientChannel clientChannel,
|
||||
int timeout = 3000,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
var waitData = clientChannel.WaitHandlePool.GetWaitDataAsync(out var sign);
|
||||
command.Sign = sign;
|
||||
WaitLock? waitLock = null;
|
||||
return GetResponsedDataAsync(this, command, clientChannel, timeout, cancellationToken);
|
||||
|
||||
try
|
||||
static async PooledValueTask<MessageBase> GetResponsedDataAsync(DeviceBase @this, ISendMessage command, IClientChannel clientChannel, int timeout, CancellationToken cancellationToken)
|
||||
{
|
||||
await BeforeSendAsync(clientChannel, cancellationToken).ConfigureAwait(false);
|
||||
var waitData = clientChannel.WaitHandlePool.GetWaitDataAsync(out var sign);
|
||||
command.Sign = sign;
|
||||
WaitLock? waitLock = null;
|
||||
|
||||
waitLock = GetWaitLock(clientChannel);
|
||||
|
||||
await waitLock.WaitAsync(cancellationToken).ConfigureAwait(false);
|
||||
|
||||
clientChannel.SetDataHandlingAdapterLogger(Logger);
|
||||
|
||||
await SendAsync(command, clientChannel, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
if (waitData.Status == WaitDataStatus.Success)
|
||||
return waitData.CompletedData;
|
||||
|
||||
var reusableTimeout = _reusableTimeouts.Get();
|
||||
var cts = reusableTimeout.GetTokenSource(timeout, cancellationToken, Channel.ClosedToken);
|
||||
try
|
||||
{
|
||||
await @this.BeforeSendAsync(clientChannel, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
await waitData.WaitAsync(cts.Token).ConfigureAwait(false);
|
||||
waitLock = @this.GetWaitLock(clientChannel);
|
||||
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
return reusableTimeout.TimeoutStatus
|
||||
? new MessageBase(new TimeoutException()) { ErrorMessage = $"Timeout, sign: {sign}" }
|
||||
: new MessageBase(new OperationCanceledException());
|
||||
await waitLock.WaitAsync(cancellationToken).ConfigureAwait(false);
|
||||
|
||||
clientChannel.SetDataHandlingAdapterLogger(@this.Logger);
|
||||
|
||||
await @this.SendAsync(command, clientChannel, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
if (waitData.Status == WaitDataStatus.Success)
|
||||
return waitData.CompletedData;
|
||||
|
||||
var reusableTimeout = @this._reusableTimeouts.Get();
|
||||
try
|
||||
{
|
||||
|
||||
var cts = reusableTimeout.GetTokenSource(timeout, cancellationToken, @this.Channel.ClosedToken);
|
||||
await waitData.WaitAsync(cts.Token).ConfigureAwait(false);
|
||||
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
return reusableTimeout.TimeoutStatus
|
||||
? new MessageBase(new TimeoutException()) { ErrorMessage = $"Timeout, sign: {sign}" }
|
||||
: new MessageBase(new OperationCanceledException());
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return new MessageBase(ex);
|
||||
}
|
||||
finally
|
||||
{
|
||||
reusableTimeout.Set();
|
||||
@this._reusableTimeouts.Return(reusableTimeout);
|
||||
}
|
||||
|
||||
if (waitData.Status == WaitDataStatus.Success)
|
||||
{
|
||||
return waitData.CompletedData;
|
||||
}
|
||||
else
|
||||
{
|
||||
var operResult = waitData.Check(reusableTimeout.TimeoutStatus);
|
||||
if (waitData.CompletedData != null)
|
||||
{
|
||||
waitData.CompletedData.ErrorMessage = $"{operResult.ErrorMessage}, sign: {sign}";
|
||||
return waitData.CompletedData;
|
||||
}
|
||||
else
|
||||
{
|
||||
return new MessageBase(new OperationCanceledException());
|
||||
}
|
||||
|
||||
//return new MessageBase(operResult) { ErrorMessage = $"{operResult.ErrorMessage}, sign: {sign}" };
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
@@ -572,39 +614,10 @@ public abstract class DeviceBase : AsyncAndSyncDisposableObject, IDevice
|
||||
}
|
||||
finally
|
||||
{
|
||||
reusableTimeout.Set();
|
||||
_reusableTimeouts.Return(reusableTimeout);
|
||||
}
|
||||
waitLock?.Release();
|
||||
waitData?.SafeDispose();
|
||||
|
||||
if (waitData.Status == WaitDataStatus.Success)
|
||||
{
|
||||
return waitData.CompletedData;
|
||||
}
|
||||
else
|
||||
{
|
||||
var operResult = waitData.Check(reusableTimeout.TimeoutStatus);
|
||||
if(waitData.CompletedData!=null)
|
||||
{
|
||||
waitData.CompletedData.ErrorMessage = $"{operResult.ErrorMessage}, sign: {sign}";
|
||||
return waitData.CompletedData;
|
||||
}
|
||||
else
|
||||
{
|
||||
return new MessageBase(new OperationCanceledException());
|
||||
}
|
||||
|
||||
//return new MessageBase(operResult) { ErrorMessage = $"{operResult.ErrorMessage}, sign: {sign}" };
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return new MessageBase(ex);
|
||||
}
|
||||
finally
|
||||
{
|
||||
waitLock?.Release();
|
||||
waitData?.SafeDispose();
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -102,33 +102,77 @@ public static partial class DeviceExtension
|
||||
public static OperResult PraseStructContent<T>(this IEnumerable<T> variables, IDevice device, ReadOnlySpan<byte> buffer, bool exWhenAny) where T : IVariable
|
||||
{
|
||||
var time = DateTime.Now;
|
||||
var result = OperResult.Success;
|
||||
foreach (var variable in variables)
|
||||
if (variables is IList<T> collection)
|
||||
{
|
||||
IThingsGatewayBitConverter byteConverter = variable.ThingsGatewayBitConverter;
|
||||
var dataType = variable.DataType;
|
||||
int index = variable.Index;
|
||||
try
|
||||
{
|
||||
var changed = byteConverter.GetChangedDataFormBytes(device, variable.RegisterAddress, buffer, index, dataType, variable.ArrayLength ?? 1, variable.RawValue, out var data);
|
||||
if (changed)
|
||||
{
|
||||
result = variable.SetValue(data, time);
|
||||
if (exWhenAny)
|
||||
if (!result.IsSuccess)
|
||||
return result;
|
||||
}
|
||||
else
|
||||
{
|
||||
variable.SetNoChangedValue(time);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return new OperResult($"Error parsing byte array, address: {variable.RegisterAddress}, array length: {buffer.Length}, index: {index}, type: {dataType}", ex);
|
||||
}
|
||||
return PraseCollection(collection, device, buffer, exWhenAny, time);
|
||||
}
|
||||
else
|
||||
{
|
||||
return PraseEnumerable(variables, device, buffer, exWhenAny, time);
|
||||
|
||||
}
|
||||
static OperResult PraseEnumerable(IEnumerable<T> variables, IDevice device, ReadOnlySpan<byte> buffer, bool exWhenAny, DateTime time)
|
||||
{
|
||||
foreach (var variable in variables)
|
||||
{
|
||||
IThingsGatewayBitConverter byteConverter = variable.ThingsGatewayBitConverter;
|
||||
var dataType = variable.DataType;
|
||||
int index = variable.Index;
|
||||
try
|
||||
{
|
||||
var changed = byteConverter.GetChangedDataFormBytes(device, variable.RegisterAddress, buffer, index, dataType, variable.ArrayLength ?? 1, variable.RawValue, out var data);
|
||||
if (changed)
|
||||
{
|
||||
var result = variable.SetValue(data, time);
|
||||
if (exWhenAny)
|
||||
if (!result.IsSuccess)
|
||||
return result;
|
||||
}
|
||||
else
|
||||
{
|
||||
variable.SetNoChangedValue(time);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return new OperResult($"Error parsing byte array, address: {variable.RegisterAddress}, array length: {buffer.Length}, index: {index}, type: {dataType}", ex);
|
||||
}
|
||||
}
|
||||
|
||||
return OperResult.Success;
|
||||
}
|
||||
static OperResult PraseCollection(IList<T> variables, IDevice device, ReadOnlySpan<byte> buffer, bool exWhenAny, DateTime time)
|
||||
{
|
||||
for (int i = 0; i < variables.Count; i++)
|
||||
{
|
||||
var variable = variables[i];
|
||||
|
||||
IThingsGatewayBitConverter byteConverter = variable.ThingsGatewayBitConverter;
|
||||
var dataType = variable.DataType;
|
||||
int index = variable.Index;
|
||||
try
|
||||
{
|
||||
var changed = byteConverter.GetChangedDataFormBytes(device, variable.RegisterAddress, buffer, index, dataType, variable.ArrayLength ?? 1, variable.RawValue, out var data);
|
||||
if (changed)
|
||||
{
|
||||
var result = variable.SetValue(data, time);
|
||||
if (exWhenAny)
|
||||
if (!result.IsSuccess)
|
||||
return result;
|
||||
}
|
||||
else
|
||||
{
|
||||
variable.SetNoChangedValue(time);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return new OperResult($"Error parsing byte array, address: {variable.RegisterAddress}, array length: {buffer.Length}, index: {index}, type: {dataType}", ex);
|
||||
}
|
||||
}
|
||||
|
||||
return OperResult.Success;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@@ -1,4 +1,6 @@
|
||||
using ThingsGateway.NewLife;
|
||||
using PooledAwait;
|
||||
|
||||
using ThingsGateway.NewLife;
|
||||
using ThingsGateway.NewLife.Threading;
|
||||
|
||||
using TouchSocket.Core;
|
||||
@@ -67,48 +69,52 @@ public class CronScheduledTask : DisposeBase, IScheduledTask
|
||||
_timer = new TimerX(TimerCallbackAsync, _state, _interval, nameof(CronScheduledTask)) { Async = true, Reentrant = false };
|
||||
}
|
||||
|
||||
private async ValueTask TimerCallbackAsync(object? state)
|
||||
private ValueTask TimerCallbackAsync(object? state)
|
||||
{
|
||||
if (Check()) return;
|
||||
if (_taskFunc == null && _valueTaskFunc == null)
|
||||
return TimerCallbackAsync(this, state);
|
||||
static async PooledValueTask TimerCallbackAsync(CronScheduledTask @this, object? state)
|
||||
{
|
||||
Dispose();
|
||||
return;
|
||||
}
|
||||
|
||||
Interlocked.Increment(ref _pendingTriggers);
|
||||
|
||||
if (Interlocked.Exchange(ref _isRunning, 1) == 1)
|
||||
return;
|
||||
|
||||
// 减少一个触发次数
|
||||
Interlocked.Decrement(ref _pendingTriggers);
|
||||
|
||||
try
|
||||
{
|
||||
if (_taskFunc != null)
|
||||
await _taskFunc(state, _token).ConfigureAwait(false);
|
||||
else if (_valueTaskFunc != null)
|
||||
await _valueTaskFunc(state, _token).ConfigureAwait(false);
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogMessage?.LogWarning(ex);
|
||||
}
|
||||
finally
|
||||
{
|
||||
Interlocked.Exchange(ref _isRunning, 0);
|
||||
}
|
||||
|
||||
if (Interlocked.Exchange(ref _pendingTriggers, 0) >= 1)
|
||||
{
|
||||
if (!Check())
|
||||
if (@this.Check()) return;
|
||||
if (@this._taskFunc == null && @this._valueTaskFunc == null)
|
||||
{
|
||||
int nextValue = next;
|
||||
SetNext(nextValue);
|
||||
@this.Dispose();
|
||||
return;
|
||||
}
|
||||
|
||||
Interlocked.Increment(ref @this._pendingTriggers);
|
||||
|
||||
if (Interlocked.Exchange(ref @this._isRunning, 1) == 1)
|
||||
return;
|
||||
|
||||
// 减少一个触发次数
|
||||
Interlocked.Decrement(ref @this._pendingTriggers);
|
||||
|
||||
try
|
||||
{
|
||||
if (@this._taskFunc != null)
|
||||
await @this._taskFunc(state, @this._token).ConfigureAwait(false);
|
||||
else if (@this._valueTaskFunc != null)
|
||||
await @this._valueTaskFunc(state, @this._token).ConfigureAwait(false);
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
@this.LogMessage?.LogWarning(ex);
|
||||
}
|
||||
finally
|
||||
{
|
||||
Interlocked.Exchange(ref @this._isRunning, 0);
|
||||
}
|
||||
|
||||
if (Interlocked.Exchange(ref @this._pendingTriggers, 0) >= 1)
|
||||
{
|
||||
if (!@this.Check())
|
||||
{
|
||||
int nextValue = @this.next;
|
||||
@this.SetNext(nextValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -1,4 +1,6 @@
|
||||
using ThingsGateway.NewLife;
|
||||
using PooledAwait;
|
||||
|
||||
using ThingsGateway.NewLife;
|
||||
using ThingsGateway.NewLife.Threading;
|
||||
|
||||
using TouchSocket.Core;
|
||||
@@ -51,50 +53,54 @@ public class ScheduledAsyncTask : DisposeBase, IScheduledTask, IScheduledIntInte
|
||||
_timer = new TimerX(DoAsync, _state, IntervalMS, IntervalMS, nameof(ScheduledAsyncTask)) { Async = true, Reentrant = false };
|
||||
}
|
||||
|
||||
private async ValueTask DoAsync(object? state)
|
||||
private ValueTask DoAsync(object? state)
|
||||
{
|
||||
if (Check())
|
||||
return;
|
||||
|
||||
if (_taskFunc == null && _valueTaskFunc == null)
|
||||
return DoAsync(this, state);
|
||||
static async PooledValueTask DoAsync(ScheduledAsyncTask @this, object? state)
|
||||
{
|
||||
Dispose();
|
||||
return;
|
||||
}
|
||||
if (@this.Check())
|
||||
return;
|
||||
|
||||
Interlocked.Increment(ref _pendingTriggers);
|
||||
|
||||
if (Interlocked.Exchange(ref _isRunning, 1) == 1)
|
||||
return;
|
||||
|
||||
// 减少一个触发次数
|
||||
Interlocked.Decrement(ref _pendingTriggers);
|
||||
|
||||
try
|
||||
{
|
||||
if (_taskFunc != null)
|
||||
await _taskFunc(state, _token).ConfigureAwait(false);
|
||||
else if (_valueTaskFunc != null)
|
||||
await _valueTaskFunc(state, _token).ConfigureAwait(false);
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogMessage?.LogWarning(ex);
|
||||
}
|
||||
finally
|
||||
{
|
||||
Interlocked.Exchange(ref _isRunning, 0);
|
||||
}
|
||||
|
||||
if (Interlocked.Exchange(ref _pendingTriggers, 0) >= 1)
|
||||
{
|
||||
if (!Check() && IntervalMS > 8)
|
||||
if (@this._taskFunc == null && @this._valueTaskFunc == null)
|
||||
{
|
||||
int nextValue = next;
|
||||
SetNext(nextValue);
|
||||
@this.Dispose();
|
||||
return;
|
||||
}
|
||||
|
||||
Interlocked.Increment(ref @this._pendingTriggers);
|
||||
|
||||
if (Interlocked.Exchange(ref @this._isRunning, 1) == 1)
|
||||
return;
|
||||
|
||||
// 减少一个触发次数
|
||||
Interlocked.Decrement(ref @this._pendingTriggers);
|
||||
|
||||
try
|
||||
{
|
||||
if (@this._taskFunc != null)
|
||||
await @this._taskFunc(state, @this._token).ConfigureAwait(false);
|
||||
else if (@this._valueTaskFunc != null)
|
||||
await @this._valueTaskFunc(state, @this._token).ConfigureAwait(false);
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
@this.LogMessage?.LogWarning(ex);
|
||||
}
|
||||
finally
|
||||
{
|
||||
Interlocked.Exchange(ref @this._isRunning, 0);
|
||||
}
|
||||
|
||||
if (Interlocked.Exchange(ref @this._pendingTriggers, 0) >= 1)
|
||||
{
|
||||
if (!@this.Check() && @this.IntervalMS > 8)
|
||||
{
|
||||
int nextValue = @this.next;
|
||||
@this.SetNext(nextValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -12,6 +12,8 @@ using Microsoft.Extensions.Logging;
|
||||
|
||||
using Newtonsoft.Json.Linq;
|
||||
|
||||
using PooledAwait;
|
||||
|
||||
using System.Collections.Concurrent;
|
||||
|
||||
using ThingsGateway.Common.Extension;
|
||||
@@ -359,314 +361,316 @@ public abstract partial class CollectBase : DriverBase
|
||||
private readonly LinkedCancellationTokenSourceCache _linkedCtsCache = new();
|
||||
|
||||
#region 执行默认读取
|
||||
//async ValueTask ReadVariableSource(object? state, CancellationToken cancellationToken)
|
||||
//{
|
||||
|
||||
// if (state is not VariableSourceRead variableSourceRead) return;
|
||||
|
||||
// if (Pause) return;
|
||||
// if (cancellationToken.IsCancellationRequested) return;
|
||||
// CancellationToken readToken = default;
|
||||
// var readerLockTask = ReadWriteLock.ReaderLockAsync(cancellationToken);
|
||||
// if (!readerLockTask.IsCompleted)
|
||||
// {
|
||||
// readToken = await readerLockTask.ConfigureAwait(false);
|
||||
// }
|
||||
// else
|
||||
// {
|
||||
// readToken = readerLockTask.Result;
|
||||
// }
|
||||
|
||||
// if (readToken.IsCancellationRequested)
|
||||
// {
|
||||
// await ReadVariableSource(state, cancellationToken).ConfigureAwait(false);
|
||||
// return;
|
||||
// }
|
||||
|
||||
// var allTokenSource = _linkedCtsCache.GetLinkedTokenSource(cancellationToken, readToken);
|
||||
// var allToken = allTokenSource.Token;
|
||||
|
||||
// //if (LogMessage?.LogLevel <= TouchSocket.Core.LogLevel.Trace)
|
||||
// // LogMessage?.Trace(string.Format("{0} - Collecting [{1} - {2}]", DeviceName, variableSourceRead?.RegisterAddress, variableSourceRead?.Length));
|
||||
|
||||
// OperResult<ReadOnlyMemory<byte>> readResult = default;
|
||||
// var readTask = ReadSourceAsync(variableSourceRead, allToken);
|
||||
// if (!readTask.IsCompleted)
|
||||
// {
|
||||
// readResult = await readTask.ConfigureAwait(false);
|
||||
// }
|
||||
// else
|
||||
// {
|
||||
// readResult = readTask.Result;
|
||||
// }
|
||||
|
||||
// var readErrorCount = 0;
|
||||
|
||||
// // 读取失败时重试一定次数
|
||||
// while (!readResult.IsSuccess && readErrorCount < CollectProperties.RetryCount)
|
||||
// {
|
||||
// if (Pause)
|
||||
// return;
|
||||
// if (cancellationToken.IsCancellationRequested)
|
||||
// return;
|
||||
|
||||
// if (readToken.IsCancellationRequested)
|
||||
// {
|
||||
// await ReadVariableSource(state, cancellationToken).ConfigureAwait(false);
|
||||
// return;
|
||||
// }
|
||||
|
||||
// readErrorCount++;
|
||||
// if (LogMessage?.LogLevel <= TouchSocket.Core.LogLevel.Trace)
|
||||
// LogMessage?.Trace(string.Format("{0} - Collection [{1} - {2}] failed - {3}", DeviceName, variableSourceRead?.RegisterAddress, variableSourceRead?.Length, readResult.ErrorMessage));
|
||||
|
||||
// //if (LogMessage?.LogLevel <= TouchSocket.Core.LogLevel.Trace)
|
||||
// // LogMessage?.Trace(string.Format("{0} - Collecting [{1} - {2}]", DeviceName, variableSourceRead?.RegisterAddress, variableSourceRead?.Length));
|
||||
// var readTask1 = ReadSourceAsync(variableSourceRead, allToken);
|
||||
// if (!readTask1.IsCompleted)
|
||||
// {
|
||||
// readResult = await readTask1.ConfigureAwait(false);
|
||||
// }
|
||||
// else
|
||||
// {
|
||||
// readResult = readTask1.Result;
|
||||
// }
|
||||
|
||||
// }
|
||||
|
||||
// if (readResult.IsSuccess)
|
||||
// {
|
||||
// // 读取成功时记录日志并增加成功计数器
|
||||
// if (LogMessage?.LogLevel <= TouchSocket.Core.LogLevel.Trace)
|
||||
// LogMessage?.Trace(string.Format("{0} - Collection [{1} - {2}] data succeeded {3}", DeviceName, variableSourceRead?.RegisterAddress, variableSourceRead?.Length, readResult.Content.Span.ToHexString(' ')));
|
||||
// CurrentDevice.SetDeviceStatus(TimerX.Now, null);
|
||||
// }
|
||||
// else
|
||||
// {
|
||||
// if (cancellationToken.IsCancellationRequested)
|
||||
// return;
|
||||
|
||||
// if (readToken.IsCancellationRequested)
|
||||
// {
|
||||
// await ReadVariableSource(state, cancellationToken).ConfigureAwait(false);
|
||||
// return;
|
||||
// }
|
||||
|
||||
// // 读取失败时记录日志并增加失败计数器,更新错误信息并清除变量状态
|
||||
// if (variableSourceRead.LastErrorMessage != readResult.ErrorMessage)
|
||||
// {
|
||||
// if (!cancellationToken.IsCancellationRequested)
|
||||
// LogMessage?.LogWarning(readResult.Exception, string.Format(AppResource.CollectFail, DeviceName, variableSourceRead?.RegisterAddress, variableSourceRead?.Length, readResult.ErrorMessage));
|
||||
// }
|
||||
// else
|
||||
// {
|
||||
// if (!cancellationToken.IsCancellationRequested)
|
||||
// {
|
||||
// if (LogMessage?.LogLevel <= TouchSocket.Core.LogLevel.Trace)
|
||||
// LogMessage?.Trace(string.Format("{0} - Collection [{1} - {2}] data failed - {3}", DeviceName, variableSourceRead?.RegisterAddress, variableSourceRead?.Length, readResult.ErrorMessage));
|
||||
// }
|
||||
// }
|
||||
|
||||
// variableSourceRead.LastErrorMessage = readResult.ErrorMessage;
|
||||
// CurrentDevice.SetDeviceStatus(TimerX.Now, null, readResult.ErrorMessage);
|
||||
// var time = DateTime.Now;
|
||||
// foreach (var item in variableSourceRead.VariableRuntimes)
|
||||
// {
|
||||
// item.SetValue(null, time, isOnline: false);
|
||||
// }
|
||||
// }
|
||||
//}
|
||||
|
||||
|
||||
|
||||
private ValueTask ReadVariableSource(object? state, CancellationToken cancellationToken)
|
||||
ValueTask ReadVariableSource(object? state, CancellationToken cancellationToken)
|
||||
{
|
||||
var enumerator = new ReadVariableSourceEnumerator(this, state, cancellationToken);
|
||||
return enumerator.MoveNextAsync();
|
||||
}
|
||||
|
||||
private struct ReadVariableSourceEnumerator
|
||||
{
|
||||
private readonly CollectBase _owner;
|
||||
private readonly object? _state;
|
||||
private readonly CancellationToken _cancellationToken;
|
||||
|
||||
private VariableSourceRead _variableSourceRead;
|
||||
private CancellationToken _readToken;
|
||||
private CancellationToken _allToken;
|
||||
private OperResult<ReadOnlyMemory<byte>> _readResult;
|
||||
private int _readErrorCount;
|
||||
private ValueTask<CancellationToken> _readerLockTask;
|
||||
private ValueTask<OperResult<ReadOnlyMemory<byte>>> _readTask;
|
||||
private int _step;
|
||||
|
||||
public ReadVariableSourceEnumerator(CollectBase owner, object? state, CancellationToken cancellationToken)
|
||||
return ReadVariableSource(this, state, cancellationToken);
|
||||
static async PooledValueTask ReadVariableSource(CollectBase @this, object? state, CancellationToken cancellationToken)
|
||||
{
|
||||
_owner = owner;
|
||||
_state = state;
|
||||
_cancellationToken = cancellationToken;
|
||||
if (state is not VariableSourceRead variableSourceRead) return;
|
||||
|
||||
_variableSourceRead = default!;
|
||||
_readToken = default;
|
||||
_allToken = default;
|
||||
_readResult = default;
|
||||
_readErrorCount = 0;
|
||||
_readerLockTask = default;
|
||||
_readTask = default;
|
||||
_step = 0;
|
||||
}
|
||||
|
||||
public ValueTask MoveNextAsync()
|
||||
{
|
||||
switch (_step)
|
||||
if (@this.Pause) return;
|
||||
if (cancellationToken.IsCancellationRequested) return;
|
||||
CancellationToken readToken = default;
|
||||
var readerLockTask = @this.ReadWriteLock.ReaderLockAsync(cancellationToken);
|
||||
if (!readerLockTask.IsCompleted)
|
||||
{
|
||||
case 0:
|
||||
if (_state is not VariableSourceRead vsr) return default;
|
||||
_variableSourceRead = vsr;
|
||||
|
||||
if (_owner.Pause) return default;
|
||||
if (_cancellationToken.IsCancellationRequested) return default;
|
||||
|
||||
#pragma warning disable CA2012 // 正确使用 ValueTask
|
||||
_readerLockTask = _owner.ReadWriteLock.ReaderLockAsync(_cancellationToken);
|
||||
#pragma warning restore CA2012 // 正确使用 ValueTask
|
||||
if (!_readerLockTask.IsCompleted)
|
||||
{
|
||||
_step = 1;
|
||||
return AwaitReaderLock();
|
||||
}
|
||||
_readToken = _readerLockTask.Result;
|
||||
goto case 2;
|
||||
|
||||
case 1:
|
||||
_readToken = _readerLockTask.Result;
|
||||
goto case 2;
|
||||
|
||||
case 2:
|
||||
if (_readToken.IsCancellationRequested)
|
||||
{
|
||||
return _owner.ReadVariableSource(_state, _cancellationToken);
|
||||
}
|
||||
|
||||
var allTokenSource = _owner._linkedCtsCache.GetLinkedTokenSource(_cancellationToken, _readToken);
|
||||
_allToken = allTokenSource.Token;
|
||||
|
||||
#pragma warning disable CA2012 // 正确使用 ValueTask
|
||||
_readTask = _owner.ReadSourceAsync(_variableSourceRead, _allToken);
|
||||
#pragma warning restore CA2012 // 正确使用 ValueTask
|
||||
if (!_readTask.IsCompleted)
|
||||
{
|
||||
_step = 3;
|
||||
return AwaitRead();
|
||||
}
|
||||
_readResult = _readTask.Result;
|
||||
goto case 4;
|
||||
|
||||
case 3:
|
||||
_readResult = _readTask.Result;
|
||||
goto case 4;
|
||||
|
||||
case 4:
|
||||
while (!_readResult.IsSuccess && _readErrorCount < _owner.CollectProperties.RetryCount)
|
||||
{
|
||||
if (_owner.Pause) return default;
|
||||
if (_cancellationToken.IsCancellationRequested) return default;
|
||||
|
||||
if (_readToken.IsCancellationRequested)
|
||||
{
|
||||
return _owner.ReadVariableSource(_state, _cancellationToken);
|
||||
}
|
||||
|
||||
_readErrorCount++;
|
||||
if (_owner.LogMessage?.LogLevel <= TouchSocket.Core.LogLevel.Trace)
|
||||
_owner.LogMessage?.Trace(string.Format("{0} - Collection [{1} - {2}] failed - {3}",
|
||||
_owner.DeviceName, _variableSourceRead?.RegisterAddress, _variableSourceRead?.Length, _readResult.ErrorMessage));
|
||||
|
||||
#pragma warning disable CA2012 // 正确使用 ValueTask
|
||||
_readTask = _owner.ReadSourceAsync(_variableSourceRead, _allToken);
|
||||
#pragma warning restore CA2012 // 正确使用 ValueTask
|
||||
if (!_readTask.IsCompleted)
|
||||
{
|
||||
_step = 5;
|
||||
return AwaitReadRetry();
|
||||
}
|
||||
_readResult = _readTask.Result;
|
||||
}
|
||||
|
||||
goto case 6;
|
||||
|
||||
case 5:
|
||||
_readResult = _readTask.Result;
|
||||
_step = 4;
|
||||
return MoveNextAsync();
|
||||
|
||||
case 6:
|
||||
if (_readResult.IsSuccess)
|
||||
{
|
||||
if (_owner.LogMessage?.LogLevel <= TouchSocket.Core.LogLevel.Trace)
|
||||
_owner.LogMessage?.Trace(string.Format("{0} - Collection [{1} - {2}] data succeeded {3}",
|
||||
_owner.DeviceName, _variableSourceRead?.RegisterAddress, _variableSourceRead?.Length, _readResult.Content.Span.ToHexString(' ')));
|
||||
|
||||
_owner.CurrentDevice.SetDeviceStatus(TimerX.Now, null);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (_cancellationToken.IsCancellationRequested) return default;
|
||||
if (_readToken.IsCancellationRequested)
|
||||
{
|
||||
return _owner.ReadVariableSource(_state, _cancellationToken);
|
||||
}
|
||||
|
||||
if (_variableSourceRead.LastErrorMessage != _readResult.ErrorMessage)
|
||||
{
|
||||
if (!_cancellationToken.IsCancellationRequested)
|
||||
_owner.LogMessage?.LogWarning(_readResult.Exception, string.Format(AppResource.CollectFail, _owner.DeviceName,
|
||||
_variableSourceRead?.RegisterAddress, _variableSourceRead?.Length, _readResult.ErrorMessage));
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!_cancellationToken.IsCancellationRequested && _owner.LogMessage?.LogLevel <= TouchSocket.Core.LogLevel.Trace)
|
||||
_owner.LogMessage?.Trace(string.Format("{0} - Collection [{1} - {2}] data failed - {3}",
|
||||
_owner.DeviceName, _variableSourceRead?.RegisterAddress, _variableSourceRead?.Length, _readResult.ErrorMessage));
|
||||
}
|
||||
|
||||
_variableSourceRead.LastErrorMessage = _readResult.ErrorMessage;
|
||||
_owner.CurrentDevice.SetDeviceStatus(TimerX.Now, null, _readResult.ErrorMessage);
|
||||
var time = DateTime.Now;
|
||||
foreach (var item in _variableSourceRead.VariableRuntimes)
|
||||
{
|
||||
item.SetValue(null, time, isOnline: false);
|
||||
}
|
||||
}
|
||||
break;
|
||||
readToken = await readerLockTask.ConfigureAwait(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
readToken = readerLockTask.Result;
|
||||
}
|
||||
|
||||
return default;
|
||||
}
|
||||
if (readToken.IsCancellationRequested)
|
||||
{
|
||||
await @this.ReadVariableSource(state, cancellationToken).ConfigureAwait(false);
|
||||
return;
|
||||
}
|
||||
|
||||
private async ValueTask AwaitReaderLock()
|
||||
{
|
||||
await _readerLockTask.ConfigureAwait(false);
|
||||
_step = 1;
|
||||
await MoveNextAsync().ConfigureAwait(false);
|
||||
}
|
||||
var allTokenSource = @this._linkedCtsCache.GetLinkedTokenSource(cancellationToken, readToken);
|
||||
var allToken = allTokenSource.Token;
|
||||
|
||||
private async ValueTask AwaitRead()
|
||||
{
|
||||
await _readTask.ConfigureAwait(false);
|
||||
_step = 3;
|
||||
await MoveNextAsync().ConfigureAwait(false);
|
||||
}
|
||||
//if (LogMessage?.LogLevel <= TouchSocket.Core.LogLevel.Trace)
|
||||
// LogMessage?.Trace(string.Format("{0} - Collecting [{1} - {2}]", DeviceName, variableSourceRead?.RegisterAddress, variableSourceRead?.Length));
|
||||
|
||||
private async ValueTask AwaitReadRetry()
|
||||
{
|
||||
await _readTask.ConfigureAwait(false);
|
||||
_step = 5;
|
||||
await MoveNextAsync().ConfigureAwait(false);
|
||||
OperResult<ReadOnlyMemory<byte>> readResult = default;
|
||||
var readTask = @this.ReadSourceAsync(variableSourceRead, allToken);
|
||||
if (!readTask.IsCompleted)
|
||||
{
|
||||
readResult = await readTask.ConfigureAwait(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
readResult = readTask.Result;
|
||||
}
|
||||
|
||||
var readErrorCount = 0;
|
||||
|
||||
// 读取失败时重试一定次数
|
||||
while (!readResult.IsSuccess && readErrorCount < @this.CollectProperties.RetryCount)
|
||||
{
|
||||
if (@this.Pause)
|
||||
return;
|
||||
if (cancellationToken.IsCancellationRequested)
|
||||
return;
|
||||
|
||||
if (readToken.IsCancellationRequested)
|
||||
{
|
||||
await @this.ReadVariableSource(state, cancellationToken).ConfigureAwait(false);
|
||||
return;
|
||||
}
|
||||
|
||||
readErrorCount++;
|
||||
if (@this.LogMessage?.LogLevel <= TouchSocket.Core.LogLevel.Trace)
|
||||
@this.LogMessage?.Trace(string.Format("{0} - Collection [{1} - {2}] failed - {3}", @this.DeviceName, variableSourceRead?.RegisterAddress, variableSourceRead?.Length, readResult.ErrorMessage));
|
||||
|
||||
//if (LogMessage?.LogLevel <= TouchSocket.Core.LogLevel.Trace)
|
||||
// LogMessage?.Trace(string.Format("{0} - Collecting [{1} - {2}]", DeviceName, variableSourceRead?.RegisterAddress, variableSourceRead?.Length));
|
||||
var readTask1 = @this.ReadSourceAsync(variableSourceRead, allToken);
|
||||
if (!readTask1.IsCompleted)
|
||||
{
|
||||
readResult = await readTask1.ConfigureAwait(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
readResult = readTask1.Result;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if (readResult.IsSuccess)
|
||||
{
|
||||
// 读取成功时记录日志并增加成功计数器
|
||||
if (@this.LogMessage?.LogLevel <= TouchSocket.Core.LogLevel.Trace)
|
||||
@this.LogMessage?.Trace(string.Format("{0} - Collection [{1} - {2}] data succeeded {3}", @this.DeviceName, variableSourceRead?.RegisterAddress, variableSourceRead?.Length, readResult.Content.Span.ToHexString(' ')));
|
||||
@this.CurrentDevice.SetDeviceStatus(TimerX.Now, null);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (cancellationToken.IsCancellationRequested)
|
||||
return;
|
||||
|
||||
if (readToken.IsCancellationRequested)
|
||||
{
|
||||
await @this.ReadVariableSource(state, cancellationToken).ConfigureAwait(false);
|
||||
return;
|
||||
}
|
||||
|
||||
// 读取失败时记录日志并增加失败计数器,更新错误信息并清除变量状态
|
||||
if (variableSourceRead.LastErrorMessage != readResult.ErrorMessage)
|
||||
{
|
||||
if (!cancellationToken.IsCancellationRequested)
|
||||
@this.LogMessage?.LogWarning(readResult.Exception, string.Format(AppResource.CollectFail, @this.DeviceName, variableSourceRead?.RegisterAddress, variableSourceRead?.Length, readResult.ErrorMessage));
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
if (@this.LogMessage?.LogLevel <= TouchSocket.Core.LogLevel.Trace)
|
||||
@this.LogMessage?.Trace(string.Format("{0} - Collection [{1} - {2}] data failed - {3}", @this.DeviceName, variableSourceRead?.RegisterAddress, variableSourceRead?.Length, readResult.ErrorMessage));
|
||||
}
|
||||
}
|
||||
|
||||
variableSourceRead.LastErrorMessage = readResult.ErrorMessage;
|
||||
@this.CurrentDevice.SetDeviceStatus(TimerX.Now, null, readResult.ErrorMessage);
|
||||
var time = DateTime.Now;
|
||||
foreach (var item in variableSourceRead.VariableRuntimes)
|
||||
{
|
||||
item.SetValue(null, time, isOnline: false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// private ValueTask ReadVariableSource(object? state, CancellationToken cancellationToken)
|
||||
// {
|
||||
// var enumerator = new ReadVariableSourceEnumerator(this, state, cancellationToken);
|
||||
// return enumerator.MoveNextAsync();
|
||||
// }
|
||||
|
||||
// private struct ReadVariableSourceEnumerator
|
||||
// {
|
||||
// private readonly CollectBase _owner;
|
||||
// private readonly object? _state;
|
||||
// private readonly CancellationToken _cancellationToken;
|
||||
|
||||
// private VariableSourceRead _variableSourceRead;
|
||||
// private CancellationToken _readToken;
|
||||
// private CancellationToken _allToken;
|
||||
// private OperResult<ReadOnlyMemory<byte>> _readResult;
|
||||
// private int _readErrorCount;
|
||||
// private ValueTask<CancellationToken> _readerLockTask;
|
||||
// private ValueTask<OperResult<ReadOnlyMemory<byte>>> _readTask;
|
||||
// private int _step;
|
||||
|
||||
// public ReadVariableSourceEnumerator(CollectBase owner, object? state, CancellationToken cancellationToken)
|
||||
// {
|
||||
// _owner = owner;
|
||||
// _state = state;
|
||||
// _cancellationToken = cancellationToken;
|
||||
|
||||
// _variableSourceRead = default!;
|
||||
// _readToken = default;
|
||||
// _allToken = default;
|
||||
// _readResult = default;
|
||||
// _readErrorCount = 0;
|
||||
// _readerLockTask = default;
|
||||
// _readTask = default;
|
||||
// _step = 0;
|
||||
// }
|
||||
|
||||
// public ValueTask MoveNextAsync()
|
||||
// {
|
||||
// switch (_step)
|
||||
// {
|
||||
// case 0:
|
||||
// if (_state is not VariableSourceRead vsr) return default;
|
||||
// _variableSourceRead = vsr;
|
||||
|
||||
// if (_owner.Pause) return default;
|
||||
// if (_cancellationToken.IsCancellationRequested) return default;
|
||||
|
||||
//#pragma warning disable CA2012 // 正确使用 ValueTask
|
||||
// _readerLockTask = _owner.ReadWriteLock.ReaderLockAsync(_cancellationToken);
|
||||
//#pragma warning restore CA2012 // 正确使用 ValueTask
|
||||
// if (!_readerLockTask.IsCompleted)
|
||||
// {
|
||||
// _step = 1;
|
||||
// return AwaitReaderLock();
|
||||
// }
|
||||
// _readToken = _readerLockTask.Result;
|
||||
// goto case 2;
|
||||
|
||||
// case 1:
|
||||
// _readToken = _readerLockTask.Result;
|
||||
// goto case 2;
|
||||
|
||||
// case 2:
|
||||
// if (_readToken.IsCancellationRequested)
|
||||
// {
|
||||
// return _owner.ReadVariableSource(_state, _cancellationToken);
|
||||
// }
|
||||
|
||||
// var allTokenSource = _owner._linkedCtsCache.GetLinkedTokenSource(_cancellationToken, _readToken);
|
||||
// _allToken = allTokenSource.Token;
|
||||
|
||||
//#pragma warning disable CA2012 // 正确使用 ValueTask
|
||||
// _readTask = _owner.ReadSourceAsync(_variableSourceRead, _allToken);
|
||||
//#pragma warning restore CA2012 // 正确使用 ValueTask
|
||||
// if (!_readTask.IsCompleted)
|
||||
// {
|
||||
// _step = 3;
|
||||
// return AwaitRead();
|
||||
// }
|
||||
// _readResult = _readTask.Result;
|
||||
// goto case 4;
|
||||
|
||||
// case 3:
|
||||
// _readResult = _readTask.Result;
|
||||
// goto case 4;
|
||||
|
||||
// case 4:
|
||||
// while (!_readResult.IsSuccess && _readErrorCount < _owner.CollectProperties.RetryCount)
|
||||
// {
|
||||
// if (_owner.Pause) return default;
|
||||
// if (_cancellationToken.IsCancellationRequested) return default;
|
||||
|
||||
// if (_readToken.IsCancellationRequested)
|
||||
// {
|
||||
// return _owner.ReadVariableSource(_state, _cancellationToken);
|
||||
// }
|
||||
|
||||
// _readErrorCount++;
|
||||
// if (_owner.LogMessage?.LogLevel <= TouchSocket.Core.LogLevel.Trace)
|
||||
// _owner.LogMessage?.Trace(string.Format("{0} - Collection [{1} - {2}] failed - {3}",
|
||||
// _owner.DeviceName, _variableSourceRead?.RegisterAddress, _variableSourceRead?.Length, _readResult.ErrorMessage));
|
||||
|
||||
//#pragma warning disable CA2012 // 正确使用 ValueTask
|
||||
// _readTask = _owner.ReadSourceAsync(_variableSourceRead, _allToken);
|
||||
//#pragma warning restore CA2012 // 正确使用 ValueTask
|
||||
// if (!_readTask.IsCompleted)
|
||||
// {
|
||||
// _step = 5;
|
||||
// return AwaitReadRetry();
|
||||
// }
|
||||
// _readResult = _readTask.Result;
|
||||
// }
|
||||
|
||||
// goto case 6;
|
||||
|
||||
// case 5:
|
||||
// _readResult = _readTask.Result;
|
||||
// _step = 4;
|
||||
// return MoveNextAsync();
|
||||
|
||||
// case 6:
|
||||
// if (_readResult.IsSuccess)
|
||||
// {
|
||||
// if (_owner.LogMessage?.LogLevel <= TouchSocket.Core.LogLevel.Trace)
|
||||
// _owner.LogMessage?.Trace(string.Format("{0} - Collection [{1} - {2}] data succeeded {3}",
|
||||
// _owner.DeviceName, _variableSourceRead?.RegisterAddress, _variableSourceRead?.Length, _readResult.Content.Span.ToHexString(' ')));
|
||||
|
||||
// _owner.CurrentDevice.SetDeviceStatus(TimerX.Now, null);
|
||||
// }
|
||||
// else
|
||||
// {
|
||||
// if (_cancellationToken.IsCancellationRequested) return default;
|
||||
// if (_readToken.IsCancellationRequested)
|
||||
// {
|
||||
// return _owner.ReadVariableSource(_state, _cancellationToken);
|
||||
// }
|
||||
|
||||
// if (_variableSourceRead.LastErrorMessage != _readResult.ErrorMessage)
|
||||
// {
|
||||
// if (!_cancellationToken.IsCancellationRequested)
|
||||
// _owner.LogMessage?.LogWarning(_readResult.Exception, string.Format(AppResource.CollectFail, _owner.DeviceName,
|
||||
// _variableSourceRead?.RegisterAddress, _variableSourceRead?.Length, _readResult.ErrorMessage));
|
||||
// }
|
||||
// else
|
||||
// {
|
||||
// if (!_cancellationToken.IsCancellationRequested && _owner.LogMessage?.LogLevel <= TouchSocket.Core.LogLevel.Trace)
|
||||
// _owner.LogMessage?.Trace(string.Format("{0} - Collection [{1} - {2}] data failed - {3}",
|
||||
// _owner.DeviceName, _variableSourceRead?.RegisterAddress, _variableSourceRead?.Length, _readResult.ErrorMessage));
|
||||
// }
|
||||
|
||||
// _variableSourceRead.LastErrorMessage = _readResult.ErrorMessage;
|
||||
// _owner.CurrentDevice.SetDeviceStatus(TimerX.Now, null, _readResult.ErrorMessage);
|
||||
// var time = DateTime.Now;
|
||||
// foreach (var item in _variableSourceRead.VariableRuntimes)
|
||||
// {
|
||||
// item.SetValue(null, time, isOnline: false);
|
||||
// }
|
||||
// }
|
||||
// break;
|
||||
// }
|
||||
|
||||
// return default;
|
||||
// }
|
||||
|
||||
// private async ValueTask AwaitReaderLock()
|
||||
// {
|
||||
// await _readerLockTask.ConfigureAwait(false);
|
||||
// _step = 1;
|
||||
// await MoveNextAsync().ConfigureAwait(false);
|
||||
// }
|
||||
|
||||
// private async ValueTask AwaitRead()
|
||||
// {
|
||||
// await _readTask.ConfigureAwait(false);
|
||||
// _step = 3;
|
||||
// await MoveNextAsync().ConfigureAwait(false);
|
||||
// }
|
||||
|
||||
// private async ValueTask AwaitReadRetry()
|
||||
// {
|
||||
// await _readTask.ConfigureAwait(false);
|
||||
// _step = 5;
|
||||
// await MoveNextAsync().ConfigureAwait(false);
|
||||
// }
|
||||
// }
|
||||
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
|
@@ -12,6 +12,8 @@ using Microsoft.Extensions.Logging;
|
||||
|
||||
using Newtonsoft.Json.Linq;
|
||||
|
||||
using PooledAwait;
|
||||
|
||||
using System.Collections.Concurrent;
|
||||
|
||||
using ThingsGateway.Common.Extension;
|
||||
@@ -149,152 +151,160 @@ public abstract class CollectFoundationBase : CollectBase
|
||||
|
||||
|
||||
|
||||
//protected override async ValueTask<OperResult<ReadOnlyMemory<byte>>> ReadSourceAsync(VariableSourceRead variableSourceRead, CancellationToken cancellationToken)
|
||||
//{
|
||||
// try
|
||||
// {
|
||||
|
||||
// if (cancellationToken.IsCancellationRequested)
|
||||
// return new(new OperationCanceledException());
|
||||
|
||||
// // 从协议读取数据
|
||||
// OperResult<ReadOnlyMemory<byte>> read = default;
|
||||
// var readTask = FoundationDevice.ReadAsync(variableSourceRead.AddressObject, cancellationToken);
|
||||
// if (!readTask.IsCompleted)
|
||||
// {
|
||||
// read = await readTask.ConfigureAwait(false);
|
||||
// }
|
||||
// else
|
||||
// {
|
||||
// read = readTask.Result;
|
||||
// }
|
||||
|
||||
// // 如果读取成功且有有效内容,则解析结构化内容
|
||||
// if (read.IsSuccess)
|
||||
// {
|
||||
// var prase = variableSourceRead.VariableRuntimes.PraseStructContent(FoundationDevice, read.Content.Span, false);
|
||||
// return new OperResult<ReadOnlyMemory<byte>>(prase);
|
||||
// }
|
||||
|
||||
// // 返回读取结果
|
||||
// return read;
|
||||
// }
|
||||
// catch (Exception ex)
|
||||
// {
|
||||
// // 捕获异常并返回失败结果
|
||||
// return new OperResult<ReadOnlyMemory<byte>>(ex);
|
||||
// }
|
||||
//}
|
||||
/// <summary>
|
||||
/// 采集驱动读取,读取成功后直接赋值变量,失败不做处理,注意非通用设备需重写
|
||||
/// </summary>
|
||||
protected override ValueTask<OperResult<ReadOnlyMemory<byte>>> ReadSourceAsync(VariableSourceRead variableSourceRead, CancellationToken cancellationToken)
|
||||
{
|
||||
if (cancellationToken.IsCancellationRequested)
|
||||
return new ValueTask<OperResult<ReadOnlyMemory<byte>>>( new OperResult<ReadOnlyMemory<byte>>(new OperationCanceledException()));
|
||||
return ReadSourceAsync(this, variableSourceRead, cancellationToken);
|
||||
|
||||
// 值类型状态机
|
||||
var stateMachine = new ReadSourceStateMachine(this, variableSourceRead, cancellationToken);
|
||||
return stateMachine.MoveNextAsync();
|
||||
}
|
||||
|
||||
private struct ReadSourceStateMachine
|
||||
{
|
||||
private readonly VariableSourceRead _variableSourceRead;
|
||||
private readonly CancellationToken _cancellationToken;
|
||||
private readonly CollectFoundationBase _owner;
|
||||
private OperResult<ReadOnlyMemory<byte>> _result;
|
||||
private ValueTask<OperResult<ReadOnlyMemory<byte>>> _readTask;
|
||||
|
||||
public ReadSourceStateMachine(CollectFoundationBase owner, VariableSourceRead variableSourceRead, CancellationToken cancellationToken)
|
||||
{
|
||||
_owner = owner;
|
||||
_variableSourceRead = variableSourceRead;
|
||||
_cancellationToken = cancellationToken;
|
||||
_result = default;
|
||||
State = 0;
|
||||
}
|
||||
|
||||
public int State { get; private set; }
|
||||
|
||||
public ValueTask<OperResult<ReadOnlyMemory<byte>>> MoveNextAsync()
|
||||
static async PooledValueTask<OperResult<ReadOnlyMemory<byte>>> ReadSourceAsync(CollectFoundationBase @this, VariableSourceRead variableSourceRead, CancellationToken cancellationToken)
|
||||
{
|
||||
try
|
||||
{
|
||||
switch (State)
|
||||
|
||||
if (cancellationToken.IsCancellationRequested)
|
||||
return new(new OperationCanceledException());
|
||||
|
||||
// 从协议读取数据
|
||||
OperResult<ReadOnlyMemory<byte>> read = default;
|
||||
var readTask = @this.FoundationDevice.ReadAsync(variableSourceRead.AddressObject, cancellationToken);
|
||||
if (!readTask.IsCompleted)
|
||||
{
|
||||
case 0:
|
||||
// 异步读取
|
||||
if (_cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
_result = new OperResult<ReadOnlyMemory<byte>>(new OperationCanceledException());
|
||||
return new ValueTask<OperResult<ReadOnlyMemory<byte>>>(_result);
|
||||
}
|
||||
|
||||
#pragma warning disable CA2012 // 正确使用 ValueTask
|
||||
_readTask = _owner.FoundationDevice.ReadAsync(_variableSourceRead.AddressObject, _cancellationToken);
|
||||
#pragma warning restore CA2012 // 正确使用 ValueTask
|
||||
|
||||
// 检查是否任务已完成
|
||||
if (_readTask.IsCompleted)
|
||||
{
|
||||
_result = _readTask.Result;
|
||||
State = 1;
|
||||
return MoveNextAsync();
|
||||
}
|
||||
|
||||
// 如果任务尚未完成,继续等待
|
||||
State = 2;
|
||||
return Awaited(_readTask);
|
||||
|
||||
case 1:
|
||||
// 解析结构化内容
|
||||
if (_result.IsSuccess)
|
||||
{
|
||||
var parsedResult = _variableSourceRead.VariableRuntimes.PraseStructContent(_owner.FoundationDevice, _result.Content.Span, false);
|
||||
return new ValueTask<OperResult<ReadOnlyMemory<byte>>>(new OperResult<ReadOnlyMemory<byte>>(parsedResult));
|
||||
}
|
||||
|
||||
return new ValueTask<OperResult<ReadOnlyMemory<byte>>>(_result);
|
||||
|
||||
case 2:
|
||||
// 完成任务后,解析内容
|
||||
_result = _readTask.Result;
|
||||
|
||||
if (_result.IsSuccess)
|
||||
{
|
||||
var parsedResult = _variableSourceRead.VariableRuntimes.PraseStructContent(_owner.FoundationDevice, _result.Content.Span, false);
|
||||
return new ValueTask<OperResult<ReadOnlyMemory<byte>>>(new OperResult<ReadOnlyMemory<byte>>(parsedResult));
|
||||
}
|
||||
|
||||
return new ValueTask<OperResult<ReadOnlyMemory<byte>>>(_result);
|
||||
|
||||
default:
|
||||
throw new InvalidOperationException("Unexpected state.");
|
||||
read = await readTask.ConfigureAwait(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
read = readTask.Result;
|
||||
}
|
||||
|
||||
// 如果读取成功且有有效内容,则解析结构化内容
|
||||
if (read.IsSuccess)
|
||||
{
|
||||
var prase = variableSourceRead.VariableRuntimes.PraseStructContent(@this.FoundationDevice, read.Content.Span, false);
|
||||
return new OperResult<ReadOnlyMemory<byte>>(prase);
|
||||
}
|
||||
|
||||
// 返回读取结果
|
||||
return read;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return new ValueTask<OperResult<ReadOnlyMemory<byte>>>(new OperResult<ReadOnlyMemory<byte>>(ex));
|
||||
}
|
||||
}
|
||||
|
||||
private async ValueTask<OperResult<ReadOnlyMemory<byte>>> Awaited(ValueTask<OperResult<ReadOnlyMemory<byte>>> vt)
|
||||
{
|
||||
try
|
||||
{
|
||||
|
||||
|
||||
await vt.ConfigureAwait(false);
|
||||
return await MoveNextAsync().ConfigureAwait(false);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
// 捕获异常并返回失败结果
|
||||
return new OperResult<ReadOnlyMemory<byte>>(ex);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
///// <summary>
|
||||
///// 采集驱动读取,读取成功后直接赋值变量,失败不做处理,注意非通用设备需重写
|
||||
///// </summary>
|
||||
// protected override ValueTask<OperResult<ReadOnlyMemory<byte>>> ReadSourceAsync(VariableSourceRead variableSourceRead, CancellationToken cancellationToken)
|
||||
// {
|
||||
// if (cancellationToken.IsCancellationRequested)
|
||||
// return new ValueTask<OperResult<ReadOnlyMemory<byte>>>( new OperResult<ReadOnlyMemory<byte>>(new OperationCanceledException()));
|
||||
|
||||
// // 值类型状态机
|
||||
// var stateMachine = new ReadSourceStateMachine(this, variableSourceRead, cancellationToken);
|
||||
// return stateMachine.MoveNextAsync();
|
||||
// }
|
||||
|
||||
// private struct ReadSourceStateMachine
|
||||
// {
|
||||
// private readonly VariableSourceRead _variableSourceRead;
|
||||
// private readonly CancellationToken _cancellationToken;
|
||||
// private readonly CollectFoundationBase _owner;
|
||||
// private OperResult<ReadOnlyMemory<byte>> _result;
|
||||
// private ValueTask<OperResult<ReadOnlyMemory<byte>>> _readTask;
|
||||
|
||||
// public ReadSourceStateMachine(CollectFoundationBase owner, VariableSourceRead variableSourceRead, CancellationToken cancellationToken)
|
||||
// {
|
||||
// _owner = owner;
|
||||
// _variableSourceRead = variableSourceRead;
|
||||
// _cancellationToken = cancellationToken;
|
||||
// _result = default;
|
||||
// State = 0;
|
||||
// }
|
||||
|
||||
// public int State { get; private set; }
|
||||
|
||||
// public ValueTask<OperResult<ReadOnlyMemory<byte>>> MoveNextAsync()
|
||||
// {
|
||||
// try
|
||||
// {
|
||||
// switch (State)
|
||||
// {
|
||||
// case 0:
|
||||
// // 异步读取
|
||||
// if (_cancellationToken.IsCancellationRequested)
|
||||
// {
|
||||
// _result = new OperResult<ReadOnlyMemory<byte>>(new OperationCanceledException());
|
||||
// return new ValueTask<OperResult<ReadOnlyMemory<byte>>>(_result);
|
||||
// }
|
||||
|
||||
//#pragma warning disable CA2012 // 正确使用 ValueTask
|
||||
// _readTask = _owner.FoundationDevice.ReadAsync(_variableSourceRead.AddressObject, _cancellationToken);
|
||||
//#pragma warning restore CA2012 // 正确使用 ValueTask
|
||||
|
||||
// // 检查是否任务已完成
|
||||
// if (_readTask.IsCompleted)
|
||||
// {
|
||||
// _result = _readTask.Result;
|
||||
// State = 1;
|
||||
// return MoveNextAsync();
|
||||
// }
|
||||
|
||||
// // 如果任务尚未完成,继续等待
|
||||
// State = 2;
|
||||
// return Awaited(_readTask);
|
||||
|
||||
// case 1:
|
||||
// // 解析结构化内容
|
||||
// if (_result.IsSuccess)
|
||||
// {
|
||||
// var parsedResult = _variableSourceRead.VariableRuntimes.PraseStructContent(_owner.FoundationDevice, _result.Content.Span, false);
|
||||
// return new ValueTask<OperResult<ReadOnlyMemory<byte>>>(new OperResult<ReadOnlyMemory<byte>>(parsedResult));
|
||||
// }
|
||||
|
||||
// return new ValueTask<OperResult<ReadOnlyMemory<byte>>>(_result);
|
||||
|
||||
// case 2:
|
||||
// // 完成任务后,解析内容
|
||||
// _result = _readTask.Result;
|
||||
|
||||
// if (_result.IsSuccess)
|
||||
// {
|
||||
// var parsedResult = _variableSourceRead.VariableRuntimes.PraseStructContent(_owner.FoundationDevice, _result.Content.Span, false);
|
||||
// return new ValueTask<OperResult<ReadOnlyMemory<byte>>>(new OperResult<ReadOnlyMemory<byte>>(parsedResult));
|
||||
// }
|
||||
|
||||
// return new ValueTask<OperResult<ReadOnlyMemory<byte>>>(_result);
|
||||
|
||||
// default:
|
||||
// throw new InvalidOperationException("Unexpected state.");
|
||||
// }
|
||||
// }
|
||||
// catch (Exception ex)
|
||||
// {
|
||||
// return new ValueTask<OperResult<ReadOnlyMemory<byte>>>(new OperResult<ReadOnlyMemory<byte>>(ex));
|
||||
// }
|
||||
// }
|
||||
|
||||
// private async ValueTask<OperResult<ReadOnlyMemory<byte>>> Awaited(ValueTask<OperResult<ReadOnlyMemory<byte>>> vt)
|
||||
// {
|
||||
// try
|
||||
// {
|
||||
|
||||
|
||||
// await vt.ConfigureAwait(false);
|
||||
// return await MoveNextAsync().ConfigureAwait(false);
|
||||
// }
|
||||
// catch (Exception ex)
|
||||
// {
|
||||
// return new OperResult<ReadOnlyMemory<byte>>(ex);
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
/// <summary>
|
||||
/// 批量写入变量值,需返回变量名称/结果,注意非通用设备需重写
|
||||
/// </summary>
|
||||
|
@@ -0,0 +1,94 @@
|
||||
//------------------------------------------------------------------------------
|
||||
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
|
||||
// 此代码版权(除特别声明外的代码)归作者本人Diego所有
|
||||
// 源代码使用协议遵循本仓库的开源协议及附加协议
|
||||
// Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway
|
||||
// Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway
|
||||
// 使用文档:https://kimdiego2098.github.io/
|
||||
// QQ群:605534569
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
namespace ThingsGateway.Foundation;
|
||||
|
||||
using BenchmarkDotNet.Attributes;
|
||||
|
||||
using PooledAwait;
|
||||
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
using ThingsGateway.NewLife;
|
||||
|
||||
[MemoryDiagnoser]
|
||||
[ThreadingDiagnoser]
|
||||
public class SemaphoreBenchmark
|
||||
{
|
||||
private readonly SemaphoreSlim _semaphoreSlim = new SemaphoreSlim(1);
|
||||
private readonly WaitLock _waitLock = new WaitLock("SemaphoreBenchmark");
|
||||
|
||||
[Params(100)]
|
||||
public int Iterations;
|
||||
|
||||
[Benchmark(Baseline = true)]
|
||||
public async Task SemaphoreSlim_WaitRelease()
|
||||
{
|
||||
|
||||
for (int i = 0; i < Iterations; i++)
|
||||
{
|
||||
await _semaphoreSlim.WaitAsync().ConfigureAwait(false);
|
||||
|
||||
await Task.Delay(1);
|
||||
_semaphoreSlim.Release();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
[Benchmark]
|
||||
public async Task ReusableAsyncSemaphore_WaitRelease()
|
||||
{
|
||||
for (int i = 0; i < Iterations; i++)
|
||||
{
|
||||
await _waitLock.WaitAsync().ConfigureAwait(false);
|
||||
|
||||
await Task.Delay(1);
|
||||
_waitLock.Release();
|
||||
}
|
||||
}
|
||||
[Benchmark]
|
||||
public async Task SemaphoreSlim_WaitReleaseToken()
|
||||
{
|
||||
|
||||
for (int i = 0; i < Iterations; i++)
|
||||
{
|
||||
using var cts = new CancellationTokenSource(1000);
|
||||
|
||||
await _semaphoreSlim.WaitAsync(cts.Token).ConfigureAwait(false);
|
||||
|
||||
await Task.Delay(1);
|
||||
_semaphoreSlim.Release();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
[Benchmark]
|
||||
public ValueTask ReusableAsyncSemaphore_WaitReleaseToken()
|
||||
{
|
||||
return ReusableAsyncSemaphore_WaitReleaseToken(this);
|
||||
|
||||
static async PooledValueTask ReusableAsyncSemaphore_WaitReleaseToken(SemaphoreBenchmark @this)
|
||||
{
|
||||
for (int i = 0; i < @this.Iterations; i++)
|
||||
{
|
||||
using var cts = new CancellationTokenSource(1000);
|
||||
|
||||
await @this._waitLock.WaitAsync(cts.Token).ConfigureAwait(false);
|
||||
|
||||
await Task.Delay(1);
|
||||
@this._waitLock.Release();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -35,11 +35,11 @@ public class TimeoutBenchmark
|
||||
}
|
||||
}
|
||||
|
||||
private ObjectPool<ReusableCancellationTokenSource> _reusableTimeouts;
|
||||
private ObjectPoolLock<ReusableCancellationTokenSource> _reusableTimeouts;
|
||||
[Benchmark]
|
||||
public async ValueTask ReusableTimeoutWaitAsync()
|
||||
{
|
||||
_reusableTimeouts ??= new();
|
||||
_reusableTimeouts ??= new ObjectPoolLock<ReusableCancellationTokenSource>();
|
||||
using var otherCts = new CancellationTokenSource();
|
||||
for (int i1 = 0; i1 < 10; i1++)
|
||||
for (int i = 0; i < 10; i++)
|
||||
|
@@ -48,10 +48,14 @@ namespace BenchmarkConsoleApp
|
||||
//.WithOptions(ConfigOptions.DisableOptimizationsValidator)
|
||||
//);
|
||||
|
||||
BenchmarkRunner.Run<ModbusBenchmark>(
|
||||
BenchmarkRunner.Run<SemaphoreBenchmark>(
|
||||
ManualConfig.Create(DefaultConfig.Instance)
|
||||
.WithOptions(ConfigOptions.DisableOptimizationsValidator)
|
||||
);
|
||||
// BenchmarkRunner.Run<ModbusBenchmark>(
|
||||
//ManualConfig.Create(DefaultConfig.Instance)
|
||||
//.WithOptions(ConfigOptions.DisableOptimizationsValidator)
|
||||
//);
|
||||
// BenchmarkRunner.Run<S7Benchmark>(
|
||||
//ManualConfig.Create(DefaultConfig.Instance)
|
||||
//.WithOptions(ConfigOptions.DisableOptimizationsValidator)
|
||||
|
@@ -1,6 +1,6 @@
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio Version 18
|
||||
VisualStudioVersion = 18.0.11018.127 d18.0
|
||||
# Visual Studio Version 17
|
||||
VisualStudioVersion = 17.14.36603.0
|
||||
MinimumVisualStudioVersion = 10.0.40219.1
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ThingsGateway.Server", "ThingsGateway.Server\ThingsGateway.Server.csproj", "{22875EFB-DADF-4612-A572-33BCC092F644}"
|
||||
EndProject
|
||||
@@ -356,7 +356,7 @@ Global
|
||||
EndGlobalSection
|
||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
SolutionGuid = {199B1B96-4F56-4828-9531-813BA02DB282}
|
||||
RESX_NeutralResourcesLanguage = zh-Hans
|
||||
RESX_Rules = {"EnabledRules":[]}
|
||||
RESX_NeutralResourcesLanguage = zh-Hans
|
||||
EndGlobalSection
|
||||
EndGlobal
|
||||
|
Reference in New Issue
Block a user