|
| 1 | +// Licensed to the .NET Foundation under one or more agreements. |
| 2 | +// The .NET Foundation licenses this file to you under the MIT license. |
| 3 | +// See the LICENSE file in the project root for more information. |
| 4 | + |
| 5 | +// Helper methods for using Tasks to implement the APM pattern. |
| 6 | +// |
| 7 | +// Example usage, wrapping a Task<int>-returning FooAsync method with Begin/EndFoo methods: |
| 8 | +// |
| 9 | +// public IAsyncResult BeginFoo(..., AsyncCallback callback, object state) |
| 10 | +// { |
| 11 | +// Task<int> t = FooAsync(...); |
| 12 | +// return TaskToApm.Begin(t, callback, state); |
| 13 | +// } |
| 14 | +// public int EndFoo(IAsyncResult asyncResult) |
| 15 | +// { |
| 16 | +// return TaskToApm.End<int>(asyncResult); |
| 17 | +// } |
| 18 | + |
| 19 | +using System.Diagnostics; |
| 20 | + |
| 21 | +namespace System.Threading.Tasks |
| 22 | +{ |
| 23 | + /// <summary> |
| 24 | + /// Provides support for efficiently using Tasks to implement the APM (Begin/End) pattern. |
| 25 | + /// </summary> |
| 26 | + internal static class TaskToApm |
| 27 | + { |
| 28 | + /// <summary> |
| 29 | + /// Marshals the Task as an IAsyncResult, using the supplied callback and state |
| 30 | + /// to implement the APM pattern. |
| 31 | + /// </summary> |
| 32 | + /// <param name="task">The Task to be marshaled.</param> |
| 33 | + /// <param name="callback">The callback to be invoked upon completion.</param> |
| 34 | + /// <param name="state">The state to be stored in the IAsyncResult.</param> |
| 35 | + /// <returns>An IAsyncResult to represent the task's asynchronous operation.</returns> |
| 36 | + public static IAsyncResult Begin(Task task, AsyncCallback callback, object state) |
| 37 | + { |
| 38 | + Debug.Assert(task != null); |
| 39 | + |
| 40 | + // If the task has already completed, then since the Task's CompletedSynchronously==false |
| 41 | + // and we want it to be true, we need to create a new IAsyncResult. (We also need the AsyncState to match.) |
| 42 | + IAsyncResult asyncResult; |
| 43 | + if (task.IsCompleted) |
| 44 | + { |
| 45 | + // Synchronous completion. |
| 46 | + asyncResult = new TaskWrapperAsyncResult(task, state, completedSynchronously: true); |
| 47 | + callback?.Invoke(asyncResult); |
| 48 | + } |
| 49 | + else |
| 50 | + { |
| 51 | + // For asynchronous completion we need to schedule a callback. Whether we can use the Task as the IAsyncResult |
| 52 | + // depends on whether the Task's AsyncState has reference equality with the requested state. |
| 53 | + asyncResult = task.AsyncState == state ? (IAsyncResult)task : new TaskWrapperAsyncResult(task, state, completedSynchronously: false); |
| 54 | + if (callback != null) |
| 55 | + { |
| 56 | + InvokeCallbackWhenTaskCompletes(task, callback, asyncResult); |
| 57 | + } |
| 58 | + } |
| 59 | + return asyncResult; |
| 60 | + } |
| 61 | + |
| 62 | + /// <summary>Processes an IAsyncResult returned by Begin.</summary> |
| 63 | + /// <param name="asyncResult">The IAsyncResult to unwrap.</param> |
| 64 | + public static void End(IAsyncResult asyncResult) |
| 65 | + { |
| 66 | + Task task; |
| 67 | + |
| 68 | + // If the IAsyncResult is our task-wrapping IAsyncResult, extract the Task. |
| 69 | + var twar = asyncResult as TaskWrapperAsyncResult; |
| 70 | + if (twar != null) |
| 71 | + { |
| 72 | + task = twar.Task; |
| 73 | + Debug.Assert(task != null, "TaskWrapperAsyncResult should never wrap a null Task."); |
| 74 | + } |
| 75 | + else |
| 76 | + { |
| 77 | + // Otherwise, the IAsyncResult should be a Task. |
| 78 | + task = asyncResult as Task; |
| 79 | + } |
| 80 | + |
| 81 | + // Make sure we actually got a task, then complete the operation by waiting on it. |
| 82 | + if (task == null) |
| 83 | + { |
| 84 | + throw new ArgumentNullException(); |
| 85 | + } |
| 86 | + |
| 87 | + task.GetAwaiter().GetResult(); |
| 88 | + } |
| 89 | + |
| 90 | + /// <summary>Processes an IAsyncResult returned by Begin.</summary> |
| 91 | + /// <param name="asyncResult">The IAsyncResult to unwrap.</param> |
| 92 | + public static TResult End<TResult>(IAsyncResult asyncResult) |
| 93 | + { |
| 94 | + Task<TResult> task; |
| 95 | + |
| 96 | + // If the IAsyncResult is our task-wrapping IAsyncResult, extract the Task. |
| 97 | + var twar = asyncResult as TaskWrapperAsyncResult; |
| 98 | + if (twar != null) |
| 99 | + { |
| 100 | + task = twar.Task as Task<TResult>; |
| 101 | + Debug.Assert(twar.Task != null, "TaskWrapperAsyncResult should never wrap a null Task."); |
| 102 | + } |
| 103 | + else |
| 104 | + { |
| 105 | + // Otherwise, the IAsyncResult should be a Task<TResult>. |
| 106 | + task = asyncResult as Task<TResult>; |
| 107 | + } |
| 108 | + |
| 109 | + // Make sure we actually got a task, then complete the operation by waiting on it. |
| 110 | + if (task == null) |
| 111 | + { |
| 112 | + throw new ArgumentNullException(); |
| 113 | + } |
| 114 | + |
| 115 | + return task.GetAwaiter().GetResult(); |
| 116 | + } |
| 117 | + |
| 118 | + /// <summary>Invokes the callback asynchronously when the task has completed.</summary> |
| 119 | + /// <param name="antecedent">The Task to await.</param> |
| 120 | + /// <param name="callback">The callback to invoke when the Task completes.</param> |
| 121 | + /// <param name="asyncResult">The Task used as the IAsyncResult.</param> |
| 122 | + private static void InvokeCallbackWhenTaskCompletes(Task antecedent, AsyncCallback callback, IAsyncResult asyncResult) |
| 123 | + { |
| 124 | + Debug.Assert(antecedent != null); |
| 125 | + Debug.Assert(callback != null); |
| 126 | + Debug.Assert(asyncResult != null); |
| 127 | + |
| 128 | + // We use OnCompleted rather than ContinueWith in order to avoid running synchronously |
| 129 | + // if the task has already completed by the time we get here. This is separated out into |
| 130 | + // its own method currently so that we only pay for the closure if necessary. |
| 131 | + antecedent.ConfigureAwait(continueOnCapturedContext: false) |
| 132 | + .GetAwaiter() |
| 133 | + .OnCompleted(() => callback(asyncResult)); |
| 134 | + |
| 135 | + // PERFORMANCE NOTE: |
| 136 | + // Assuming we're in the default ExecutionContext, the "slow path" of an incomplete |
| 137 | + // task will result in four allocations: the new IAsyncResult, the delegate+closure |
| 138 | + // in this method, and the continuation object inside of OnCompleted (necessary |
| 139 | + // to capture both the Action delegate and the ExecutionContext in a single object). |
| 140 | + // In the future, if performance requirements drove a need, those four |
| 141 | + // allocations could be reduced to one. This would be achieved by having TaskWrapperAsyncResult |
| 142 | + // also implement ITaskCompletionAction (and optionally IThreadPoolWorkItem). It would need |
| 143 | + // additional fields to store the AsyncCallback and an ExecutionContext. Once configured, |
| 144 | + // it would be set into the Task as a continuation. Its Invoke method would then be run when |
| 145 | + // the antecedent completed, and, doing all of the necessary work to flow ExecutionContext, |
| 146 | + // it would invoke the AsyncCallback. It could also have a field on it for the antecedent, |
| 147 | + // so that the End method would have access to the completed antecedent. For related examples, |
| 148 | + // see other implementations of ITaskCompletionAction, and in particular ReadWriteTask |
| 149 | + // used in Stream.Begin/EndXx's implementation. |
| 150 | + } |
| 151 | + |
| 152 | + /// <summary> |
| 153 | + /// Provides a simple IAsyncResult that wraps a Task. This, in effect, allows |
| 154 | + /// for overriding what's seen for the CompletedSynchronously and AsyncState values. |
| 155 | + /// </summary> |
| 156 | + private sealed class TaskWrapperAsyncResult : IAsyncResult |
| 157 | + { |
| 158 | + /// <summary>The wrapped Task.</summary> |
| 159 | + internal readonly Task Task; |
| 160 | + /// <summary>The new AsyncState value.</summary> |
| 161 | + private readonly object _state; |
| 162 | + /// <summary>The new CompletedSynchronously value.</summary> |
| 163 | + private readonly bool _completedSynchronously; |
| 164 | + |
| 165 | + /// <summary>Initializes the IAsyncResult with the Task to wrap and the overriding AsyncState and CompletedSynchronously values.</summary> |
| 166 | + /// <param name="task">The Task to wrap.</param> |
| 167 | + /// <param name="state">The new AsyncState value</param> |
| 168 | + /// <param name="completedSynchronously">The new CompletedSynchronously value.</param> |
| 169 | + internal TaskWrapperAsyncResult(Task task, object state, bool completedSynchronously) |
| 170 | + { |
| 171 | + Debug.Assert(task != null); |
| 172 | + Debug.Assert(!completedSynchronously || task.IsCompleted, "If completedSynchronously is true, the task must be completed."); |
| 173 | + |
| 174 | + this.Task = task; |
| 175 | + _state = state; |
| 176 | + _completedSynchronously = completedSynchronously; |
| 177 | + } |
| 178 | + |
| 179 | + // The IAsyncResult implementation. |
| 180 | + // - IsCompleted and AsyncWaitHandle just pass through to the Task. |
| 181 | + // - AsyncState and CompletedSynchronously return the corresponding values stored in this object. |
| 182 | + |
| 183 | + object IAsyncResult.AsyncState { get { return _state; } } |
| 184 | + bool IAsyncResult.CompletedSynchronously { get { return _completedSynchronously; } } |
| 185 | + bool IAsyncResult.IsCompleted { get { return this.Task.IsCompleted; } } |
| 186 | + WaitHandle IAsyncResult.AsyncWaitHandle { get { return ((IAsyncResult)this.Task).AsyncWaitHandle; } } |
| 187 | + } |
| 188 | + } |
| 189 | +} |
0 commit comments