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