|
| 1 | +#pragma warning disable |
| 2 | +#if !NET8_0_OR_GREATER |
| 3 | +// Copied verbatim from https://github.com/dotnet/runtime/blob/78bd7debe6d8b454294c673c9cb969c6b8a14692/src/libraries/Common/src/System/Threading/Tasks/TaskToAsyncResult.cs |
| 4 | + |
| 5 | +// Licensed to the .NET Foundation under one or more agreements. |
| 6 | +// The .NET Foundation licenses this file to you under the MIT license. |
| 7 | + |
| 8 | +using System.Diagnostics; |
| 9 | + |
| 10 | +namespace System.Threading.Tasks |
| 11 | +{ |
| 12 | + /// <summary> |
| 13 | + /// Provides methods for using <see cref="Task"/> to implement the Asynchronous Programming Model |
| 14 | + /// pattern based on "Begin" and "End" methods. |
| 15 | + /// </summary> |
| 16 | +#if SYSTEM_PRIVATE_CORELIB |
| 17 | + public |
| 18 | +#else |
| 19 | + internal |
| 20 | +#endif |
| 21 | + static class TaskToAsyncResult |
| 22 | + { |
| 23 | + /// <summary>Creates a new <see cref="IAsyncResult"/> from the specified <see cref="Task"/>, optionally invoking <paramref name="callback"/> when the task has completed.</summary> |
| 24 | + /// <param name="task">The <see cref="Task"/> to be wrapped in an <see cref="IAsyncResult"/>.</param> |
| 25 | + /// <param name="callback">The callback to be invoked upon <paramref name="task"/>'s completion. If <see langword="null"/>, no callback will be invoked.</param> |
| 26 | + /// <param name="state">The state to be stored in the <see cref="IAsyncResult"/>.</param> |
| 27 | + /// <returns>An <see cref="IAsyncResult"/> to represent the task's asynchronous operation. This instance will also be passed to <paramref name="callback"/> when it's invoked.</returns> |
| 28 | + /// <exception cref="ArgumentNullException"><paramref name="task"/> is null.</exception> |
| 29 | + /// <remarks> |
| 30 | + /// In conjunction with the <see cref="End(IAsyncResult)"/> or <see cref="End{TResult}(IAsyncResult)"/> methods, this method may be used |
| 31 | + /// to implement the Begin/End pattern (also known as the Asynchronous Programming Model pattern, or APM). It is recommended to not expose this pattern |
| 32 | + /// in new code; the methods on <see cref="TaskToAsyncResult"/> are intended only to help implement such Begin/End methods when they must be exposed, for example |
| 33 | + /// because a base class provides virtual methods for the pattern, or when they've already been exposed and must remain for compatibility. These methods enable |
| 34 | + /// implementing all of the core asynchronous logic via <see cref="Task"/>s and then easily implementing Begin/End methods around that functionality. |
| 35 | + /// </remarks> |
| 36 | + public static IAsyncResult Begin(Task task, AsyncCallback? callback, object? state) |
| 37 | + { |
| 38 | +#if NET6_0_OR_GREATER |
| 39 | + ArgumentNullException.ThrowIfNull(task); |
| 40 | +#else |
| 41 | + if (task is null) |
| 42 | + { |
| 43 | + throw new ArgumentNullException(nameof(task)); |
| 44 | + } |
| 45 | +#endif |
| 46 | + |
| 47 | + return new TaskAsyncResult(task, state, callback); |
| 48 | + } |
| 49 | + |
| 50 | + /// <summary>Waits for the <see cref="Task"/> wrapped by the <see cref="IAsyncResult"/> returned by <see cref="Begin"/> to complete.</summary> |
| 51 | + /// <param name="asyncResult">The <see cref="IAsyncResult"/> for which to wait.</param> |
| 52 | + /// <exception cref="ArgumentNullException"><paramref name="asyncResult"/> is null.</exception> |
| 53 | + /// <exception cref="ArgumentException"><paramref name="asyncResult"/> was not produced by a call to <see cref="Begin"/>.</exception> |
| 54 | + /// <remarks>This will propagate any exception stored in the wrapped <see cref="Task"/>.</remarks> |
| 55 | + public static void End(IAsyncResult asyncResult) => |
| 56 | + Unwrap(asyncResult).GetAwaiter().GetResult(); |
| 57 | + |
| 58 | + /// <summary>Waits for the <see cref="Task{TResult}"/> wrapped by the <see cref="IAsyncResult"/> returned by <see cref="Begin"/> to complete.</summary> |
| 59 | + /// <typeparam name="TResult">The type of the result produced.</typeparam> |
| 60 | + /// <param name="asyncResult">The <see cref="IAsyncResult"/> for which to wait.</param> |
| 61 | + /// <returns>The result of the <see cref="Task{TResult}"/> wrapped by the <see cref="IAsyncResult"/>.</returns> |
| 62 | + /// <exception cref="ArgumentNullException"><paramref name="asyncResult"/> is null.</exception> |
| 63 | + /// <exception cref="ArgumentException"><paramref name="asyncResult"/> was not produced by a call to <see cref="Begin"/>.</exception> |
| 64 | + /// <remarks>This will propagate any exception stored in the wrapped <see cref="Task{TResult}"/>.</remarks> |
| 65 | + public static TResult End<TResult>(IAsyncResult asyncResult) => |
| 66 | + Unwrap<TResult>(asyncResult).GetAwaiter().GetResult(); |
| 67 | + |
| 68 | + /// <summary>Extracts the underlying <see cref="Task"/> from an <see cref="IAsyncResult"/> created by <see cref="Begin"/>.</summary> |
| 69 | + /// <param name="asyncResult">The <see cref="IAsyncResult"/> created by <see cref="Begin"/>.</param> |
| 70 | + /// <returns>The <see cref="Task"/> wrapped by the <see cref="IAsyncResult"/>.</returns> |
| 71 | + /// <exception cref="ArgumentNullException"><paramref name="asyncResult"/> is null.</exception> |
| 72 | + /// <exception cref="ArgumentException"><paramref name="asyncResult"/> was not produced by a call to <see cref="Begin"/>.</exception> |
| 73 | + public static Task Unwrap(IAsyncResult asyncResult) |
| 74 | + { |
| 75 | +#if NET6_0_OR_GREATER |
| 76 | + ArgumentNullException.ThrowIfNull(asyncResult); |
| 77 | +#else |
| 78 | + if (asyncResult is null) |
| 79 | + { |
| 80 | + throw new ArgumentNullException(nameof(asyncResult)); |
| 81 | + } |
| 82 | +#endif |
| 83 | + |
| 84 | + if ((asyncResult as TaskAsyncResult)?._task is not Task task) |
| 85 | + { |
| 86 | + throw new ArgumentException(null, nameof(asyncResult)); |
| 87 | + } |
| 88 | + |
| 89 | + return task; |
| 90 | + } |
| 91 | + |
| 92 | + /// <summary>Extracts the underlying <see cref="Task{TResult}"/> from an <see cref="IAsyncResult"/> created by <see cref="Begin"/>.</summary> |
| 93 | + /// <typeparam name="TResult">The type of the result produced by the returned task.</typeparam> |
| 94 | + /// <param name="asyncResult">The <see cref="IAsyncResult"/> created by <see cref="Begin"/>.</param> |
| 95 | + /// <returns>The <see cref="Task{TResult}"/> wrapped by the <see cref="IAsyncResult"/>.</returns> |
| 96 | + /// <exception cref="ArgumentNullException"><paramref name="asyncResult"/> is null.</exception> |
| 97 | + /// <exception cref="ArgumentException"> |
| 98 | + /// <paramref name="asyncResult"/> was not produced by a call to <see cref="Begin"/>, |
| 99 | + /// or the <see cref="Task{TResult}"/> provided to <see cref="Begin"/> was used a generic type parameter |
| 100 | + /// that's different from the <typeparamref name="TResult"/> supplied to this call. |
| 101 | + /// </exception> |
| 102 | + public static Task<TResult> Unwrap<TResult>(IAsyncResult asyncResult) |
| 103 | + { |
| 104 | +#if NET6_0_OR_GREATER |
| 105 | + ArgumentNullException.ThrowIfNull(asyncResult); |
| 106 | +#else |
| 107 | + if (asyncResult is null) |
| 108 | + { |
| 109 | + throw new ArgumentNullException(nameof(asyncResult)); |
| 110 | + } |
| 111 | +#endif |
| 112 | + |
| 113 | + if ((asyncResult as TaskAsyncResult)?._task is not Task<TResult> task) |
| 114 | + { |
| 115 | + throw new ArgumentException(null, nameof(asyncResult)); |
| 116 | + } |
| 117 | + |
| 118 | + return task; |
| 119 | + } |
| 120 | + |
| 121 | + /// <summary>Provides a simple <see cref="IAsyncResult"/> that wraps a <see cref="Task"/>.</summary> |
| 122 | + /// <remarks> |
| 123 | + /// We could use the Task as the IAsyncResult if the Task's AsyncState is the same as the object state, |
| 124 | + /// but that's very rare, in particular in a situation where someone cares about allocation, and always |
| 125 | + /// using TaskAsyncResult simplifies things and enables additional optimizations. |
| 126 | + /// </remarks> |
| 127 | + private sealed class TaskAsyncResult : IAsyncResult |
| 128 | + { |
| 129 | + /// <summary>The wrapped Task.</summary> |
| 130 | + internal readonly Task _task; |
| 131 | + /// <summary>Callback to invoke when the wrapped task completes.</summary> |
| 132 | + private readonly AsyncCallback? _callback; |
| 133 | + |
| 134 | + /// <summary>Initializes the IAsyncResult with the Task to wrap and the associated object state.</summary> |
| 135 | + /// <param name="task">The Task to wrap.</param> |
| 136 | + /// <param name="state">The new AsyncState value.</param> |
| 137 | + /// <param name="callback">Callback to invoke when the wrapped task completes.</param> |
| 138 | + internal TaskAsyncResult(Task task, object? state, AsyncCallback? callback) |
| 139 | + { |
| 140 | + Debug.Assert(task is not null); |
| 141 | + |
| 142 | + _task = task; |
| 143 | + AsyncState = state; |
| 144 | + |
| 145 | + if (task.IsCompleted) |
| 146 | + { |
| 147 | + // The task has already completed. Treat this as synchronous completion. |
| 148 | + // Invoke the callback; no need to store it. |
| 149 | + CompletedSynchronously = true; |
| 150 | + callback?.Invoke(this); |
| 151 | + } |
| 152 | + else if (callback is not null) |
| 153 | + { |
| 154 | + // Asynchronous completion, and we have a callback; schedule it. We use OnCompleted rather than ContinueWith in |
| 155 | + // order to avoid running synchronously if the task has already completed by the time we get here but still run |
| 156 | + // synchronously as part of the task's completion if the task completes after (the more common case). |
| 157 | + _callback = callback; |
| 158 | + _task.ConfigureAwait(continueOnCapturedContext: false) |
| 159 | + .GetAwaiter() |
| 160 | + .OnCompleted(() => _callback.Invoke(this)); |
| 161 | + } |
| 162 | + } |
| 163 | + |
| 164 | + /// <inheritdoc/> |
| 165 | + public object? AsyncState { get; } |
| 166 | + |
| 167 | + /// <inheritdoc/> |
| 168 | + public bool CompletedSynchronously { get; } |
| 169 | + |
| 170 | + /// <inheritdoc/> |
| 171 | + public bool IsCompleted => _task.IsCompleted; |
| 172 | + |
| 173 | + /// <inheritdoc/> |
| 174 | + public WaitHandle AsyncWaitHandle => ((IAsyncResult) _task).AsyncWaitHandle; |
| 175 | + } |
| 176 | + } |
| 177 | +} |
| 178 | +#endif |
0 commit comments