// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // Helper methods for using Tasks to implement the APM pattern. // // Example usage, wrapping a Task-returning FooAsync method with Begin/EndFoo methods: // // public IAsyncResult BeginFoo(..., AsyncCallback? callback, object? state) => // TaskToApm.Begin(FooAsync(...), callback, state); // // public int EndFoo(IAsyncResult asyncResult) => // TaskToApm.End(asyncResult); // ReSharper disable RedundantUsingDirective // ReSharper disable ConvertTypeCheckPatternToNullCheck // ReSharper disable ConditionIsAlwaysTrueOrFalse #pragma warning disable CS8602 #pragma warning disable CS8601 #pragma warning disable CS8618 #nullable enable using System.Diagnostics; using System.Diagnostics.CodeAnalysis; namespace System.Threading.Tasks { /// /// Provides support for efficiently using Tasks to implement the APM (Begin/End) pattern. /// internal 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 (GetTask(asyncResult) is Task t) { t.GetAwaiter().GetResult(); return; } ThrowArgumentException(asyncResult); } /// Processes an IAsyncResult returned by Begin. /// The IAsyncResult to unwrap. public static TResult End(IAsyncResult asyncResult) { if (GetTask(asyncResult) is Task task) { return task.GetAwaiter().GetResult(); } ThrowArgumentException(asyncResult); return default!; // unreachable } /// Gets the task represented by the IAsyncResult. public static Task? GetTask(IAsyncResult asyncResult) => (asyncResult as TaskAsyncResult)?._task; /// Throws an argument exception for the invalid . #if !UNITY_NETFRAMEWORK [DoesNotReturn] #endif private static void ThrowArgumentException(IAsyncResult asyncResult) => throw (asyncResult is null ? new ArgumentNullException(nameof(asyncResult)) : new ArgumentException(null, nameof(asyncResult))); /// 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; } } }