2
0

TaskToAsyncResult.cs 9.6 KB

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