File: system\threading\Tasks\TaskToApm.cs
Project: ndp\clr\src\bcl\mscorlib.csproj (mscorlib)
// ==++==
//
//   Copyright (c) Microsoft Corporation.  All rights reserved.
// 
// ==--==
// =+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
//
// TaskToApm.cs
//
// <OWNER>Microsoft</OWNER>
//
// Helper methods for using Tasks to implement the APM pattern.
//
// Example usage, wrapping a Task<int>-returning FooAsync method with Begin/EndFoo methods:
//     public IAsyncResult BeginFoo(..., AsyncCallback callback, object state)
//     {
//         Task<int> t = FooAsync(...);
//         return TaskToApm.Begin(t, callback, state);
//     }
//     public int EndFoo(IAsyncResult asyncResult)
//     {
//         return TaskToApm.End<int>(asyncResult);
//     }
//
// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
 
using System.IO;
using System.Diagnostics.Contracts;
 
namespace System.Threading.Tasks
{
    /// <summary>
    /// Provides support for efficiently using Tasks to implement the APM (Begin/End) pattern.
    /// </summary>
    internal static class TaskToApm
    {
        /// <summary>
        /// Marshals the Task as an IAsyncResult, using the supplied callback and state
        /// to implement the APM pattern.
        /// </summary>
        /// <param name="task">The Task to be marshaled.</param>
        /// <param name="callback">The callback to be invoked upon completion.</param>
        /// <param name="state">The state to be stored in the IAsyncResult.</param>
        /// <returns>An IAsyncResult to represent the task's asynchronous operation.</returns>
        public static IAsyncResult Begin(Task task, AsyncCallback callback, object state)
        {
            Contract.Requires(task != null);
 
            // If the task has already completed, then since the Task's CompletedSynchronously==false
            // and we want it to be true, we need to create a new IAsyncResult. (We also need the AsyncState to match.)
            IAsyncResult asyncResult;
            if (task.IsCompleted)
            {
                // Synchronous completion
                asyncResult = new TaskWrapperAsyncResult(task, state, completedSynchronously: true);
                if (callback != null)
                    callback(asyncResult);
            }
            // Otherwise, we need to schedule a callback.  Whether we can use the Task as the IAsyncResult
            // depends on whether the Task's AsyncState has reference equality with the requested state.
            else
            {
                // Asynchronous completion
                asyncResult = task.AsyncState == state ? (IAsyncResult)task : new TaskWrapperAsyncResult(task, state, completedSynchronously: false);
                if (callback != null) 
                    InvokeCallbackWhenTaskCompletes(task, callback, asyncResult);
            }
            return asyncResult;
        }
 
        /// <summary>Processes an IAsyncResult returned by Begin.</summary>
        /// <param name="asyncResult">The IAsyncResult to unwrap.</param>
        public static void End(IAsyncResult asyncResult)
        {
            Task task;
 
            // If the IAsyncResult is our task-wrapping IAsyncResult, extract the Task.
            var twar = asyncResult as TaskWrapperAsyncResult;
            if (twar != null)
            {
                task = twar.Task;
                Contract.Assert(task != null, "TaskWrapperAsyncResult should never wrap a null Task.");
            }
            // Otherwise, the IAsyncResult should be a Task.
            else
            {
                task = asyncResult as Task;
            }
 
            // Make sure we actually got a task, then complete the operation by waiting on it.
            if (task == null)
                __Error.WrongAsyncResult();
            task.GetAwaiter().GetResult();
        }
 
        /// <summary>Processes an IAsyncResult returned by Begin.</summary>
        /// <param name="asyncResult">The IAsyncResult to unwrap.</param>
        public static TResult End<TResult>(IAsyncResult asyncResult)
        {
            Task<TResult> task;
 
            // If the IAsyncResult is our task-wrapping IAsyncResult, extract the Task.
            var twar = asyncResult as TaskWrapperAsyncResult;
            if (twar != null)
            {
                task = twar.Task as Task<TResult>;
                Contract.Assert(twar.Task != null, "TaskWrapperAsyncResult should never wrap a null Task.");
            }
            // Otherwise, the IAsyncResult should be a Task<TResult>.
            else
            {
                task = asyncResult as Task<TResult>;
            }
 
            // Make sure we actually got a task, then complete the operation by waiting on it.
            if (task == null)
                __Error.WrongAsyncResult();
            return task.GetAwaiter().GetResult();
        }
 
        /// <summary>Invokes the callback asynchronously when the task has completed.</summary>
        /// <param name="antecedent">The Task to await.</param>
        /// <param name="callback">The callback to invoke when the Task completes.</param>
        /// <param name="asyncResult">The Task used as the IAsyncResult.</param>
        private static void InvokeCallbackWhenTaskCompletes(Task antecedent, AsyncCallback callback, IAsyncResult asyncResult)
        {
            Contract.Requires(antecedent != null);
            Contract.Requires(callback != null);
            Contract.Requires(asyncResult != null);
 
            // We use OnCompleted rather than ContinueWith in order to avoid running synchronously
            // if the task has already completed by the time we get here.  This is separated out into
            // its own method currently so that we only pay for the closure if necessary.
            antecedent.ConfigureAwait(continueOnCapturedContext:false)
                      .GetAwaiter()
                      .OnCompleted(() => callback(asyncResult));
 
            // PERFORMANCE NOTE:
            // Assuming we're in the default ExecutionContext, the "slow path" of an incomplete
            // task will result in four allocations: the new IAsyncResult,  the delegate+closure
            // in this method, and the continuation object inside of OnCompleted (necessary
            // to capture both the Action delegate and the ExecutionContext in a single object).  
            // In the future, if performance requirements drove a need, those four 
            // allocations could be reduced to one.  This would be achieved by having TaskWrapperAsyncResult
            // also implement ITaskCompletionAction (and optionally IThreadPoolWorkItem).  It would need
            // additional fields to store the AsyncCallback and an ExecutionContext.  Once configured, 
            // it would be set into the Task as a continuation.  Its Invoke method would then be run when 
            // the antecedent completed, and, doing all of the necessary work to flow ExecutionContext, 
            // it would invoke the AsyncCallback.  It could also have a field on it for the antecedent, 
            // so that the End method would have access to the completed antecedent. For related examples, 
            // see other implementations of ITaskCompletionAction, and in particular ReadWriteTask 
            // used in Stream.Begin/EndXx's implementation.
        }
 
        /// <summary>
        /// Provides a simple IAsyncResult that wraps a Task.  This, in effect, allows
        /// for overriding what's seen for the CompletedSynchronously and AsyncState values.
        /// </summary>
        private sealed class TaskWrapperAsyncResult : IAsyncResult
        {
            /// <summary>The wrapped Task.</summary>
            internal readonly Task Task;
            /// <summary>The new AsyncState value.</summary>
            private readonly object m_state;
            /// <summary>The new CompletedSynchronously value.</summary>
            private readonly bool m_completedSynchronously;
 
            /// <summary>Initializes the IAsyncResult with the Task to wrap and the overriding AsyncState and CompletedSynchronously values.</summary>
            /// <param name="task">The Task to wrap.</param>
            /// <param name="state">The new AsyncState value</param>
            /// <param name="completedSynchronously">The new CompletedSynchronously value.</param>
            internal TaskWrapperAsyncResult(Task task, object state, bool completedSynchronously) 
            {
                Contract.Requires(task != null);
                Contract.Requires(!completedSynchronously || task.IsCompleted, "If completedSynchronously is true, the task must be completed.");
 
                this.Task = task;
                m_state = state;
                m_completedSynchronously = completedSynchronously;
            }
 
            // The IAsyncResult implementation.  
            // - IsCompleted and AsyncWaitHandle just pass through to the Task.
            // - AsyncState and CompletedSynchronously return the corresponding values stored in this object.
 
            object IAsyncResult.AsyncState { get { return m_state; } }
            bool IAsyncResult.CompletedSynchronously { get { return m_completedSynchronously; } }
            bool IAsyncResult.IsCompleted { get { return this.Task.IsCompleted; } }
            WaitHandle IAsyncResult.AsyncWaitHandle { get { return ((IAsyncResult)this.Task).AsyncWaitHandle; } }
        }
    }
}