using System;
using System.Diagnostics;
using System.Threading;
using System.Threading.Tasks;
namespace FastGithub.FlowAnalyze
{
static class TaskToApm
{
///
/// Marshals the Task as an IAsyncResult, using the supplied callback and state
/// to implement the APM pattern.
///
/// The Task to be marshaled.
/// The callback to be invoked upon completion.
/// The state to be stored in the IAsyncResult.
/// An IAsyncResult to represent the task's asynchronous operation.
public static IAsyncResult Begin(Task task, AsyncCallback? callback, object? state) =>
new TaskAsyncResult(task, state, callback);
/// Processes an IAsyncResult returned by Begin.
/// The IAsyncResult to unwrap.
public static void End(IAsyncResult asyncResult)
{
if (asyncResult is TaskAsyncResult twar)
{
twar._task.GetAwaiter().GetResult();
return;
}
throw new ArgumentNullException();
}
/// Processes an IAsyncResult returned by Begin.
/// The IAsyncResult to unwrap.
public static TResult End(IAsyncResult asyncResult)
{
if (asyncResult is TaskAsyncResult twar && twar._task is Task task)
{
return task.GetAwaiter().GetResult();
}
throw new ArgumentNullException();
}
/// Provides a simple IAsyncResult that wraps a Task.
///
/// We could use the Task as the IAsyncResult if the Task's AsyncState is the same as the object state,
/// but that's very rare, in particular in a situation where someone cares about allocation, and always
/// using TaskAsyncResult simplifies things and enables additional optimizations.
///
internal sealed class TaskAsyncResult : IAsyncResult
{
/// The wrapped Task.
internal readonly Task _task;
/// Callback to invoke when the wrapped task completes.
private readonly AsyncCallback? _callback;
/// Initializes the IAsyncResult with the Task to wrap and the associated object state.
/// The Task to wrap.
/// The new AsyncState value.
/// Callback to invoke when the wrapped task completes.
internal TaskAsyncResult(Task task, object? state, AsyncCallback? callback)
{
Debug.Assert(task != null);
_task = task;
AsyncState = state;
if (task.IsCompleted)
{
// Synchronous completion. Invoke the callback. No need to store it.
CompletedSynchronously = true;
callback?.Invoke(this);
}
else if (callback != null)
{
// Asynchronous completion, and we have a callback; schedule it. We use OnCompleted rather than ContinueWith in
// order to avoid running synchronously if the task has already completed by the time we get here but still run
// synchronously as part of the task's completion if the task completes after (the more common case).
_callback = callback;
_task.ConfigureAwait(continueOnCapturedContext: false)
.GetAwaiter()
.OnCompleted(InvokeCallback); // allocates a delegate, but avoids a closure
}
}
/// Invokes the callback.
private void InvokeCallback()
{
Debug.Assert(!CompletedSynchronously);
Debug.Assert(_callback != null);
_callback.Invoke(this);
}
/// Gets a user-defined object that qualifies or contains information about an asynchronous operation.
public object? AsyncState { get; }
/// Gets a value that indicates whether the asynchronous operation completed synchronously.
/// This is set lazily based on whether the has completed by the time this object is created.
public bool CompletedSynchronously { get; }
/// Gets a value that indicates whether the asynchronous operation has completed.
public bool IsCompleted => _task.IsCompleted;
/// Gets a that is used to wait for an asynchronous operation to complete.
public WaitHandle AsyncWaitHandle => ((IAsyncResult)_task).AsyncWaitHandle;
}
}
}