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);
}
}
}