using System; using System.Collections.Generic; using System.Runtime.CompilerServices; using System.Text; using System.Threading; using System.Threading.Tasks; namespace Capnp.Util { /// /// A task-like object which enforces that all await operations from the same thread leave in the exact order they were issued. /// Note that an ordinary .NET Task does not fulfill this requirement if completed by a thread which is different from the /// awaiting thread. /// public class StrictlyOrderedAwaitTask: INotifyCompletion { class Cover { } class Seal { } static readonly Cover s_cover = new Cover(); static readonly Seal s_seal = new Seal(); readonly Task _awaitedTask; object? _state; /// /// Constructs an instance /// /// Task on which the order shall be enforced public StrictlyOrderedAwaitTask(Task awaitedTask) { _awaitedTask = awaitedTask; _state = s_cover; } /// /// await pattern implementation /// /// An object suitable for the await pattern public StrictlyOrderedAwaitTask GetAwaiter() { return this; } async void AwaitInternal() { try { await _awaitedTask; } catch { } finally { #if SOTASK_PERF long outerCount = 0, innerCount = 0; #endif SpinWait.SpinUntil(() => { Action? continuations; while (true) { #if SOTASK_PERF ++innerCount; #endif continuations = (Action?)Interlocked.Exchange(ref _state, null); if (continuations != null) continuations(); else break; } #if SOTASK_PERF ++outerCount; #endif return Interlocked.CompareExchange(ref _state, s_seal, null) == null; }); #if SOTASK_PERF StrictlyOrderedTaskExtensions.Stats.UpdateAwaitInternal(outerCount, innerCount); #endif } } /// /// Part of await pattern implementation. Do not use directly. /// public void OnCompleted(Action continuation) { bool first = false; #if SOTASK_PERF long spinCount = 0; #endif SpinWait.SpinUntil(() => { #if SOTASK_PERF ++spinCount; #endif object? cur, next; cur = Volatile.Read(ref _state); first = false; switch (cur) { case Cover cover: next = continuation; first = true; break; case null: next = continuation; break; case Action action: next = action + continuation; break; default: continuation(); return true; } return Interlocked.CompareExchange(ref _state, next, cur) == cur; }); #if SOTASK_PERF StrictlyOrderedTaskExtensions.Stats.UpdateOnCompleted(spinCount); #endif if (first) { AwaitInternal(); } } /// /// Whether the underlying task did complete and it is safe to skip continuation registration. /// public bool IsCompleted => _awaitedTask.IsCompleted && (_state == s_cover || _state == s_seal); /// /// Part of await pattern implementation. Do not use directly. /// public void GetResult() => _awaitedTask.GetAwaiter().GetResult(); /// /// Task on which the order shall be enforced. /// public Task WrappedTask => _awaitedTask; } /// /// A task-like object which enforces that all await operations from the same thread leave in the exact order they were issued. /// Note that an ordinary .NET Task does not fulfill this requirement if completed by a thread which is different from the /// awaiting thread. /// public class StrictlyOrderedAwaitTask : StrictlyOrderedAwaitTask { /// /// Constructs an instance /// /// Task on which the order shall be enforced public StrictlyOrderedAwaitTask(Task awaitedTask): base(awaitedTask) { } /// /// Task on which the order shall be enforced. /// public new Task WrappedTask => (Task)base.WrappedTask; /// /// await pattern implementation /// /// An object suitable for the await pattern public new StrictlyOrderedAwaitTask GetAwaiter() => this; /// /// Part of await pattern implementation. Do not use directly. /// public new T GetResult() => WrappedTask.GetAwaiter().GetResult(); /// /// Redirects to the wrapped Task's result. /// public T Result => WrappedTask.Result; } /// /// Extension methods to simplify the use of /// public static class StrictlyOrderedTaskExtensions { #if SOTASK_PERF public class Statistics { internal long _awaitInternalMaxOuterIterations; internal long _awaitInternalMaxInnerIterations; internal long _onCompletedMaxSpins; public long AwaitInternalMaxOuterIterations => Volatile.Read(ref _awaitInternalMaxOuterIterations); public long AwaitInternalMaxInnerIterations => Volatile.Read(ref _awaitInternalMaxInnerIterations); public long OnCompletedMaxSpins => Volatile.Read(ref _onCompletedMaxSpins); public void Reset() { Volatile.Write(ref _awaitInternalMaxOuterIterations, 0); Volatile.Write(ref _awaitInternalMaxInnerIterations, 0); Volatile.Write(ref _onCompletedMaxSpins, 0); } internal static void InterlockedMax(ref long current, long value) { long existing; do { existing = Volatile.Read(ref current); if (value <= existing) return; } while (Interlocked.CompareExchange(ref current, value, existing) != existing); } internal void UpdateAwaitInternal(long outerCount, long innerCount) { InterlockedMax(ref _awaitInternalMaxOuterIterations, outerCount); InterlockedMax(ref _awaitInternalMaxInnerIterations, innerCount); } internal void UpdateOnCompleted(long spinCount) { InterlockedMax(ref _onCompletedMaxSpins, spinCount); } } /// /// Performance profiling statistics /// public static readonly Statistics Stats = new Statistics(); #endif /// /// Converts the task to a task-like object which enforces that all await operations from the same thread leave in the exact order they were issued. /// /// The type of the result produced by the Task /// Task to wrap /// awaitable object public static StrictlyOrderedAwaitTask EnforceAwaitOrder(this Task task) { return new StrictlyOrderedAwaitTask(task); } /// /// Converts the task to a task-like object which enforces that all await operations from the same thread leave in the exact order they were issued. /// /// Task to wrap /// awaitable object public static StrictlyOrderedAwaitTask EnforceAwaitOrder(this Task task) { return new StrictlyOrderedAwaitTask(task); } } }