diff options
Diffstat (limited to 'src/mscorlib/src/System/Threading/Tasks/TaskToApm.cs')
-rw-r--r-- | src/mscorlib/src/System/Threading/Tasks/TaskToApm.cs | 189 |
1 files changed, 189 insertions, 0 deletions
diff --git a/src/mscorlib/src/System/Threading/Tasks/TaskToApm.cs b/src/mscorlib/src/System/Threading/Tasks/TaskToApm.cs new file mode 100644 index 0000000000..02b130c297 --- /dev/null +++ b/src/mscorlib/src/System/Threading/Tasks/TaskToApm.cs @@ -0,0 +1,189 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +// =+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+ +// +// +// +// 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; } } + } + } +} |