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.
5 // =+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
9 // Compiler-targeted types that build tasks for use as the return types of asynchronous methods.
11 // =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
13 using System.Diagnostics;
14 using System.Diagnostics.Tracing;
15 using System.Reflection;
16 using System.Runtime.ExceptionServices;
17 #if FEATURE_COMINTEROP
18 using System.Runtime.InteropServices.WindowsRuntime;
19 #endif // FEATURE_COMINTEROP
20 using System.Threading;
21 using System.Threading.Tasks;
23 using Internal.Runtime.CompilerServices;
25 namespace System.Runtime.CompilerServices
28 /// Provides a builder for asynchronous methods that return void.
29 /// This type is intended for compiler use only.
31 public struct AsyncVoidMethodBuilder
33 /// <summary>The synchronization context associated with this operation.</summary>
34 private SynchronizationContext _synchronizationContext;
35 /// <summary>The builder this void builder wraps.</summary>
36 private AsyncTaskMethodBuilder _builder; // mutable struct: must not be readonly
38 /// <summary>Initializes a new <see cref="AsyncVoidMethodBuilder"/>.</summary>
39 /// <returns>The initialized <see cref="AsyncVoidMethodBuilder"/>.</returns>
40 public static AsyncVoidMethodBuilder Create()
42 SynchronizationContext sc = SynchronizationContext.Current;
43 sc?.OperationStarted();
44 return new AsyncVoidMethodBuilder() { _synchronizationContext = sc };
47 /// <summary>Initiates the builder's execution with the associated state machine.</summary>
48 /// <typeparam name="TStateMachine">Specifies the type of the state machine.</typeparam>
49 /// <param name="stateMachine">The state machine instance, passed by reference.</param>
50 /// <exception cref="System.ArgumentNullException">The <paramref name="stateMachine"/> argument was null (Nothing in Visual Basic).</exception>
52 [MethodImpl(MethodImplOptions.AggressiveInlining)]
53 public void Start<TStateMachine>(ref TStateMachine stateMachine) where TStateMachine : IAsyncStateMachine =>
54 AsyncMethodBuilder.Start(ref stateMachine);
56 /// <summary>Associates the builder with the state machine it represents.</summary>
57 /// <param name="stateMachine">The heap-allocated state machine object.</param>
58 /// <exception cref="System.ArgumentNullException">The <paramref name="stateMachine"/> argument was null (Nothing in Visual Basic).</exception>
59 /// <exception cref="System.InvalidOperationException">The builder is incorrectly initialized.</exception>
60 public void SetStateMachine(IAsyncStateMachine stateMachine) =>
61 _builder.SetStateMachine(stateMachine);
64 /// Schedules the specified state machine to be pushed forward when the specified awaiter completes.
66 /// <typeparam name="TAwaiter">Specifies the type of the awaiter.</typeparam>
67 /// <typeparam name="TStateMachine">Specifies the type of the state machine.</typeparam>
68 /// <param name="awaiter">The awaiter.</param>
69 /// <param name="stateMachine">The state machine.</param>
70 public void AwaitOnCompleted<TAwaiter, TStateMachine>(
71 ref TAwaiter awaiter, ref TStateMachine stateMachine)
72 where TAwaiter : INotifyCompletion
73 where TStateMachine : IAsyncStateMachine =>
74 _builder.AwaitOnCompleted(ref awaiter, ref stateMachine);
77 /// Schedules the specified state machine to be pushed forward when the specified awaiter completes.
79 /// <typeparam name="TAwaiter">Specifies the type of the awaiter.</typeparam>
80 /// <typeparam name="TStateMachine">Specifies the type of the state machine.</typeparam>
81 /// <param name="awaiter">The awaiter.</param>
82 /// <param name="stateMachine">The state machine.</param>
83 public void AwaitUnsafeOnCompleted<TAwaiter, TStateMachine>(
84 ref TAwaiter awaiter, ref TStateMachine stateMachine)
85 where TAwaiter : ICriticalNotifyCompletion
86 where TStateMachine : IAsyncStateMachine =>
87 _builder.AwaitUnsafeOnCompleted(ref awaiter, ref stateMachine);
89 /// <summary>Completes the method builder successfully.</summary>
90 public void SetResult()
92 if (AsyncCausalityTracer.LoggingOn)
94 AsyncCausalityTracer.TraceOperationCompletion(CausalityTraceLevel.Required, this.Task.Id, AsyncCausalityStatus.Completed);
97 // Mark the builder as completed. As this is a void-returning method, this mostly
98 // doesn't matter, but it can affect things like debug events related to finalization.
101 if (_synchronizationContext != null)
103 NotifySynchronizationContextOfCompletion();
107 /// <summary>Faults the method builder with an exception.</summary>
108 /// <param name="exception">The exception that is the cause of this fault.</param>
109 /// <exception cref="System.ArgumentNullException">The <paramref name="exception"/> argument is null (Nothing in Visual Basic).</exception>
110 /// <exception cref="System.InvalidOperationException">The builder is not initialized.</exception>
111 public void SetException(Exception exception)
113 if (exception == null)
115 ThrowHelper.ThrowArgumentNullException(ExceptionArgument.exception);
118 if (AsyncCausalityTracer.LoggingOn)
120 AsyncCausalityTracer.TraceOperationCompletion(CausalityTraceLevel.Required, this.Task.Id, AsyncCausalityStatus.Error);
123 if (_synchronizationContext != null)
125 // If we captured a synchronization context, Post the throwing of the exception to it
126 // and decrement its outstanding operation count.
129 AsyncMethodBuilderCore.ThrowAsync(exception, targetContext: _synchronizationContext);
133 NotifySynchronizationContextOfCompletion();
138 // Otherwise, queue the exception to be thrown on the ThreadPool. This will
139 // result in a crash unless legacy exception behavior is enabled by a config
140 // file or a CLR host.
141 AsyncMethodBuilderCore.ThrowAsync(exception, targetContext: null);
144 // The exception was propagated already; we don't need or want to fault the builder, just mark it as completed.
145 _builder.SetResult();
148 /// <summary>Notifies the current synchronization context that the operation completed.</summary>
149 private void NotifySynchronizationContextOfCompletion()
151 Debug.Assert(_synchronizationContext != null, "Must only be used with a non-null context.");
154 _synchronizationContext.OperationCompleted();
156 catch (Exception exc)
158 // If the interaction with the SynchronizationContext goes awry,
159 // fall back to propagating on the ThreadPool.
160 AsyncMethodBuilderCore.ThrowAsync(exc, targetContext: null);
164 /// <summary>Lazily instantiate the Task in a non-thread-safe manner.</summary>
165 private Task Task => _builder.Task;
168 /// Gets an object that may be used to uniquely identify this builder to the debugger.
171 /// This property lazily instantiates the ID in a non-thread-safe manner.
172 /// It must only be used by the debugger and AsyncCausalityTracer in a single-threaded manner.
174 internal object ObjectIdForDebugger => _builder.ObjectIdForDebugger;
178 /// Provides a builder for asynchronous methods that return <see cref="System.Threading.Tasks.Task"/>.
179 /// This type is intended for compiler use only.
182 /// AsyncTaskMethodBuilder is a value type, and thus it is copied by value.
183 /// Prior to being copied, one of its Task, SetResult, or SetException members must be accessed,
184 /// or else the copies may end up building distinct Task instances.
186 public struct AsyncTaskMethodBuilder
188 /// <summary>A cached VoidTaskResult task used for builders that complete synchronously.</summary>
189 private readonly static Task<VoidTaskResult> s_cachedCompleted = AsyncTaskMethodBuilder<VoidTaskResult>.s_defaultResultTask;
191 /// <summary>The generic builder object to which this non-generic instance delegates.</summary>
192 private AsyncTaskMethodBuilder<VoidTaskResult> m_builder; // mutable struct: must not be readonly. Debugger depends on the exact name of this field.
194 /// <summary>Initializes a new <see cref="AsyncTaskMethodBuilder"/>.</summary>
195 /// <returns>The initialized <see cref="AsyncTaskMethodBuilder"/>.</returns>
196 public static AsyncTaskMethodBuilder Create() => default(AsyncTaskMethodBuilder);
198 /// <summary>Initiates the builder's execution with the associated state machine.</summary>
199 /// <typeparam name="TStateMachine">Specifies the type of the state machine.</typeparam>
200 /// <param name="stateMachine">The state machine instance, passed by reference.</param>
201 [DebuggerStepThrough]
202 [MethodImpl(MethodImplOptions.AggressiveInlining)]
203 public void Start<TStateMachine>(ref TStateMachine stateMachine) where TStateMachine : IAsyncStateMachine =>
204 AsyncMethodBuilder.Start(ref stateMachine);
206 /// <summary>Associates the builder with the state machine it represents.</summary>
207 /// <param name="stateMachine">The heap-allocated state machine object.</param>
208 /// <exception cref="System.ArgumentNullException">The <paramref name="stateMachine"/> argument was null (Nothing in Visual Basic).</exception>
209 /// <exception cref="System.InvalidOperationException">The builder is incorrectly initialized.</exception>
210 public void SetStateMachine(IAsyncStateMachine stateMachine) =>
211 m_builder.SetStateMachine(stateMachine);
214 /// Schedules the specified state machine to be pushed forward when the specified awaiter completes.
216 /// <typeparam name="TAwaiter">Specifies the type of the awaiter.</typeparam>
217 /// <typeparam name="TStateMachine">Specifies the type of the state machine.</typeparam>
218 /// <param name="awaiter">The awaiter.</param>
219 /// <param name="stateMachine">The state machine.</param>
220 public void AwaitOnCompleted<TAwaiter, TStateMachine>(
221 ref TAwaiter awaiter, ref TStateMachine stateMachine)
222 where TAwaiter : INotifyCompletion
223 where TStateMachine : IAsyncStateMachine =>
224 m_builder.AwaitOnCompleted(ref awaiter, ref stateMachine);
227 /// Schedules the specified state machine to be pushed forward when the specified awaiter completes.
229 /// <typeparam name="TAwaiter">Specifies the type of the awaiter.</typeparam>
230 /// <typeparam name="TStateMachine">Specifies the type of the state machine.</typeparam>
231 /// <param name="awaiter">The awaiter.</param>
232 /// <param name="stateMachine">The state machine.</param>
233 public void AwaitUnsafeOnCompleted<TAwaiter, TStateMachine>(
234 ref TAwaiter awaiter, ref TStateMachine stateMachine)
235 where TAwaiter : ICriticalNotifyCompletion
236 where TStateMachine : IAsyncStateMachine =>
237 m_builder.AwaitUnsafeOnCompleted(ref awaiter, ref stateMachine);
239 /// <summary>Gets the <see cref="System.Threading.Tasks.Task"/> for this builder.</summary>
240 /// <returns>The <see cref="System.Threading.Tasks.Task"/> representing the builder's asynchronous operation.</returns>
241 /// <exception cref="System.InvalidOperationException">The builder is not initialized.</exception>
244 [MethodImpl(MethodImplOptions.AggressiveInlining)]
245 get => m_builder.Task;
249 /// Completes the <see cref="System.Threading.Tasks.Task"/> in the
250 /// <see cref="System.Threading.Tasks.TaskStatus">RanToCompletion</see> state.
252 /// <exception cref="System.InvalidOperationException">The builder is not initialized.</exception>
253 /// <exception cref="System.InvalidOperationException">The task has already completed.</exception>
254 public void SetResult() => m_builder.SetResult(s_cachedCompleted); // Using s_cachedCompleted is faster than using s_defaultResultTask.
257 /// Completes the <see cref="System.Threading.Tasks.Task"/> in the
258 /// <see cref="System.Threading.Tasks.TaskStatus">Faulted</see> state with the specified exception.
260 /// <param name="exception">The <see cref="System.Exception"/> to use to fault the task.</param>
261 /// <exception cref="System.ArgumentNullException">The <paramref name="exception"/> argument is null (Nothing in Visual Basic).</exception>
262 /// <exception cref="System.InvalidOperationException">The builder is not initialized.</exception>
263 /// <exception cref="System.InvalidOperationException">The task has already completed.</exception>
264 public void SetException(Exception exception) => m_builder.SetException(exception);
267 /// Called by the debugger to request notification when the first wait operation
268 /// (await, Wait, Result, etc.) on this builder's task completes.
270 /// <param name="enabled">
271 /// true to enable notification; false to disable a previously set notification.
273 internal void SetNotificationForWaitCompletion(bool enabled) => m_builder.SetNotificationForWaitCompletion(enabled);
276 /// Gets an object that may be used to uniquely identify this builder to the debugger.
279 /// This property lazily instantiates the ID in a non-thread-safe manner.
280 /// It must only be used by the debugger and tracing purposes, and only in a single-threaded manner
281 /// when no other threads are in the middle of accessing this property or this.Task.
283 internal object ObjectIdForDebugger => m_builder.ObjectIdForDebugger;
287 /// Provides a builder for asynchronous methods that return <see cref="System.Threading.Tasks.Task{TResult}"/>.
288 /// This type is intended for compiler use only.
291 /// AsyncTaskMethodBuilder{TResult} is a value type, and thus it is copied by value.
292 /// Prior to being copied, one of its Task, SetResult, or SetException members must be accessed,
293 /// or else the copies may end up building distinct Task instances.
295 public struct AsyncTaskMethodBuilder<TResult>
297 /// <summary>A cached task for default(TResult).</summary>
298 internal readonly static Task<TResult> s_defaultResultTask = AsyncTaskCache.CreateCacheableTask(default(TResult));
300 /// <summary>The lazily-initialized built task.</summary>
301 private Task<TResult> m_task; // lazily-initialized: must not be readonly. Debugger depends on the exact name of this field.
303 /// <summary>Initializes a new <see cref="AsyncTaskMethodBuilder"/>.</summary>
304 /// <returns>The initialized <see cref="AsyncTaskMethodBuilder"/>.</returns>
305 public static AsyncTaskMethodBuilder<TResult> Create()
307 return default(AsyncTaskMethodBuilder<TResult>);
308 // NOTE: If this method is ever updated to perform more initialization,
309 // other Create methods like AsyncTaskMethodBuilder.Create and
310 // AsyncValueTaskMethodBuilder.Create must be updated to call this.
313 /// <summary>Initiates the builder's execution with the associated state machine.</summary>
314 /// <typeparam name="TStateMachine">Specifies the type of the state machine.</typeparam>
315 /// <param name="stateMachine">The state machine instance, passed by reference.</param>
316 [DebuggerStepThrough]
317 [MethodImpl(MethodImplOptions.AggressiveInlining)]
318 public void Start<TStateMachine>(ref TStateMachine stateMachine) where TStateMachine : IAsyncStateMachine =>
319 AsyncMethodBuilder.Start(ref stateMachine);
321 /// <summary>Associates the builder with the state machine it represents.</summary>
322 /// <param name="stateMachine">The heap-allocated state machine object.</param>
323 /// <exception cref="System.ArgumentNullException">The <paramref name="stateMachine"/> argument was null (Nothing in Visual Basic).</exception>
324 /// <exception cref="System.InvalidOperationException">The builder is incorrectly initialized.</exception>
325 public void SetStateMachine(IAsyncStateMachine stateMachine)
327 if (stateMachine == null)
329 ThrowHelper.ThrowArgumentNullException(ExceptionArgument.stateMachine);
334 ThrowHelper.ThrowInvalidOperationException(ExceptionResource.AsyncMethodBuilder_InstanceNotInitialized);
337 // SetStateMachine was originally needed in order to store the boxed state machine reference into
338 // the boxed copy. Now that a normal box is no longer used, SetStateMachine is also legacy. We need not
339 // do anything here, and thus assert to ensure we're not calling this from our own implementations.
340 Debug.Fail("SetStateMachine should not be used.");
344 /// Schedules the specified state machine to be pushed forward when the specified awaiter completes.
346 /// <typeparam name="TAwaiter">Specifies the type of the awaiter.</typeparam>
347 /// <typeparam name="TStateMachine">Specifies the type of the state machine.</typeparam>
348 /// <param name="awaiter">The awaiter.</param>
349 /// <param name="stateMachine">The state machine.</param>
350 public void AwaitOnCompleted<TAwaiter, TStateMachine>(
351 ref TAwaiter awaiter, ref TStateMachine stateMachine)
352 where TAwaiter : INotifyCompletion
353 where TStateMachine : IAsyncStateMachine
357 awaiter.OnCompleted(GetStateMachineBox(ref stateMachine).MoveNextAction);
361 AsyncMethodBuilderCore.ThrowAsync(e, targetContext: null);
366 /// Schedules the specified state machine to be pushed forward when the specified awaiter completes.
368 /// <typeparam name="TAwaiter">Specifies the type of the awaiter.</typeparam>
369 /// <typeparam name="TStateMachine">Specifies the type of the state machine.</typeparam>
370 /// <param name="awaiter">The awaiter.</param>
371 /// <param name="stateMachine">The state machine.</param>
372 public void AwaitUnsafeOnCompleted<TAwaiter, TStateMachine>(
373 ref TAwaiter awaiter, ref TStateMachine stateMachine)
374 where TAwaiter : ICriticalNotifyCompletion
375 where TStateMachine : IAsyncStateMachine
377 IAsyncStateMachineBox box = GetStateMachineBox(ref stateMachine);
379 // The null tests here ensure that the jit can optimize away the interface
380 // tests when TAwaiter is is a ref type.
381 if ((null != (object)default(TAwaiter)) && (awaiter is ITaskAwaiter))
383 ref TaskAwaiter ta = ref Unsafe.As<TAwaiter, TaskAwaiter>(ref awaiter); // relies on TaskAwaiter/TaskAwaiter<T> having the same layout
384 TaskAwaiter.UnsafeOnCompletedInternal(ta.m_task, box, continueOnCapturedContext: true);
386 else if ((null != (object)default(TAwaiter)) && (awaiter is IConfiguredTaskAwaiter))
388 ref ConfiguredTaskAwaitable.ConfiguredTaskAwaiter ta = ref Unsafe.As<TAwaiter, ConfiguredTaskAwaitable.ConfiguredTaskAwaiter>(ref awaiter);
389 TaskAwaiter.UnsafeOnCompletedInternal(ta.m_task, box, ta.m_continueOnCapturedContext);
391 else if ((null != (object)default(TAwaiter)) && (awaiter is IValueTaskAwaiter))
393 Task t = ((IValueTaskAwaiter)awaiter).GetTask();
394 TaskAwaiter.UnsafeOnCompletedInternal(t, box, continueOnCapturedContext: true);
396 else if ((null != (object)default(TAwaiter)) && (awaiter is IConfiguredValueTaskAwaiter))
398 Task t = ((IConfiguredValueTaskAwaiter)awaiter).GetTask(out bool continueOnCapturedContext);
399 TaskAwaiter.UnsafeOnCompletedInternal(t, box, continueOnCapturedContext);
401 // The awaiter isn't specially known. Fall back to doing a normal await.
406 awaiter.UnsafeOnCompleted(box.MoveNextAction);
410 AsyncMethodBuilderCore.ThrowAsync(e, targetContext: null);
415 /// <summary>Gets the "boxed" state machine object.</summary>
416 /// <typeparam name="TStateMachine">Specifies the type of the async state machine.</typeparam>
417 /// <param name="stateMachine">The state machine.</param>
418 /// <returns>The "boxed" state machine.</returns>
419 private IAsyncStateMachineBox GetStateMachineBox<TStateMachine>(
420 ref TStateMachine stateMachine)
421 where TStateMachine : IAsyncStateMachine
423 ExecutionContext currentContext = ExecutionContext.Capture();
425 // Check first for the most common case: not the first yield in an async method.
426 // In this case, the first yield will have already "boxed" the state machine in
427 // a strongly-typed manner into an AsyncStateMachineBox. It will already contain
428 // the state machine as well as a MoveNextDelegate and a context. The only thing
429 // we might need to do is update the context if that's changed since it was stored.
430 if (m_task is AsyncStateMachineBox<TStateMachine> stronglyTypedBox)
432 if (stronglyTypedBox.Context != currentContext)
434 stronglyTypedBox.Context = currentContext;
436 return stronglyTypedBox;
439 // The least common case: we have a weakly-typed boxed. This results if the debugger
440 // or some other use of reflection accesses a property like ObjectIdForDebugger or a
441 // method like SetNotificationForWaitCompletion prior to the first await happening. In
442 // such situations, we need to get an object to represent the builder, but we don't yet
443 // know the type of the state machine, and thus can't use TStateMachine. Instead, we
444 // use the IAsyncStateMachine interface, which all TStateMachines implement. This will
445 // result in a boxing allocation when storing the TStateMachine if it's a struct, but
446 // this only happens in active debugging scenarios where such performance impact doesn't
448 if (m_task is AsyncStateMachineBox<IAsyncStateMachine> weaklyTypedBox)
450 // If this is the first await, we won't yet have a state machine, so store it.
451 if (weaklyTypedBox.StateMachine == null)
453 Debugger.NotifyOfCrossThreadDependency(); // same explanation as with usage below
454 weaklyTypedBox.StateMachine = stateMachine;
457 // Update the context. This only happens with a debugger, so no need to spend
458 // extra IL checking for equality before doing the assignment.
459 weaklyTypedBox.Context = currentContext;
460 return weaklyTypedBox;
463 // Alert a listening debugger that we can't make forward progress unless it slips threads.
464 // If we don't do this, and a method that uses "await foo;" is invoked through funceval,
465 // we could end up hooking up a callback to push forward the async method's state machine,
466 // the debugger would then abort the funceval after it takes too long, and then continuing
467 // execution could result in another callback being hooked up. At that point we have
468 // multiple callbacks registered to push the state machine, which could result in bad behavior.
469 Debugger.NotifyOfCrossThreadDependency();
471 // At this point, m_task should really be null, in which case we want to create the box.
472 // However, in a variety of debugger-related (erroneous) situations, it might be non-null,
473 // e.g. if the Task property is examined in a Watch window, forcing it to be lazily-intialized
474 // as a Task<TResult> rather than as an AsyncStateMachineBox. The worst that happens in such
475 // cases is we lose the ability to properly step in the debugger, as the debugger uses that
476 // object's identity to track this specific builder/state machine. As such, we proceed to
477 // overwrite whatever's there anyway, even if it's non-null.
478 var box = AsyncMethodBuilderCore.TrackAsyncMethodCompletion ?
479 new DebugFinalizableAsyncStateMachineBox<TStateMachine>() :
480 new AsyncStateMachineBox<TStateMachine>();
481 m_task = box; // important: this must be done before storing stateMachine into box.StateMachine!
482 box.StateMachine = stateMachine;
483 box.Context = currentContext;
488 /// Provides an async state machine box with a finalizer that will fire an EventSource
489 /// event about the state machine if it's being finalized without having been completed.
491 /// <typeparam name="TStateMachine">Specifies the type of the state machine.</typeparam>
492 private sealed class DebugFinalizableAsyncStateMachineBox<TStateMachine> :
493 AsyncStateMachineBox<TStateMachine>
494 where TStateMachine : IAsyncStateMachine
496 ~DebugFinalizableAsyncStateMachineBox()
498 // If the state machine is being finalized, something went wrong during its processing,
499 // e.g. it awaited something that got collected without itself having been completed.
500 // Fire an event with details about the state machine to help with debugging.
501 if (!IsCompleted) // double-check it's not completed, just to help minimize false positives
503 TplEtwProvider.Log.IncompleteAsyncMethod(this);
508 /// <summary>A strongly-typed box for Task-based async state machines.</summary>
509 /// <typeparam name="TStateMachine">Specifies the type of the state machine.</typeparam>
510 /// <typeparam name="TResult">Specifies the type of the Task's result.</typeparam>
511 private class AsyncStateMachineBox<TStateMachine> :
512 Task<TResult>, IAsyncStateMachineBox
513 where TStateMachine : IAsyncStateMachine
515 /// <summary>Delegate used to invoke on an ExecutionContext when passed an instance of this box type.</summary>
516 private static readonly ContextCallback s_callback = s => ((AsyncStateMachineBox<TStateMachine>)s).StateMachine.MoveNext();
518 /// <summary>A delegate to the <see cref="MoveNext"/> method.</summary>
519 private Action _moveNextAction;
520 /// <summary>The state machine itself.</summary>
521 public TStateMachine StateMachine; // mutable struct; do not make this readonly
522 /// <summary>Captured ExecutionContext with which to invoke <see cref="MoveNextAction"/>; may be null.</summary>
523 public ExecutionContext Context;
525 /// <summary>A delegate to the <see cref="MoveNext"/> method.</summary>
526 public Action MoveNextAction =>
528 (_moveNextAction = AsyncCausalityTracer.LoggingOn ? AsyncMethodBuilderCore.OutputAsyncCausalityEvents(this, new Action(MoveNext)) : new Action(MoveNext));
530 /// <summary>Calls MoveNext on <see cref="StateMachine"/></summary>
531 public void MoveNext()
533 ExecutionContext context = Context;
536 StateMachine.MoveNext();
540 ExecutionContext.RunInternal(context, s_callback, this);
543 // In case this is a state machine box with a finalizer, suppress its finalization
544 // if it's now complete. We only need the finalizer to run if the box is collected
545 // without having been completed.
546 if (IsCompleted && AsyncMethodBuilderCore.TrackAsyncMethodCompletion)
548 GC.SuppressFinalize(this);
553 /// Calls MoveNext on <see cref="StateMachine"/>. Implements ITaskCompletionAction.Invoke so
554 /// that the state machine object may be queued directly as a continuation into a Task's
555 /// continuation slot/list.
557 /// <param name="completedTask">The completing task that caused this method to be invoked, if there was one.</param>
558 void ITaskCompletionAction.Invoke(Task completedTask) => MoveNext();
560 /// <summary>Signals to Task's continuation logic that <see cref="Invoke"/> runs arbitrary user code via MoveNext.</summary>
561 bool ITaskCompletionAction.InvokeMayRunArbitraryCode => true;
563 /// <summary>Gets the state machine as a boxed object. This should only be used for debugging purposes.</summary>
564 IAsyncStateMachine IAsyncStateMachineBox.GetStateMachineObject() => StateMachine; // likely boxes, only use for debugging
567 /// <summary>Gets the <see cref="System.Threading.Tasks.Task{TResult}"/> for this builder.</summary>
568 /// <returns>The <see cref="System.Threading.Tasks.Task{TResult}"/> representing the builder's asynchronous operation.</returns>
569 public Task<TResult> Task
571 [MethodImpl(MethodImplOptions.AggressiveInlining)]
572 get => m_task ?? InitializeTaskAsPromise();
576 /// Initializes the task, which must not yet be initialized. Used only when the Task is being forced into
577 /// existence when no state machine is needed, e.g. when the builder is being synchronously completed with
578 /// an exception, when the builder is being used out of the context of an async method, etc.
580 [MethodImpl(MethodImplOptions.NoInlining)]
581 private Task<TResult> InitializeTaskAsPromise()
583 Debug.Assert(m_task == null);
584 return (m_task = new Task<TResult>());
588 /// Initializes the task, which must not yet be initialized. Used only when the Task is being forced into
589 /// existence due to the debugger trying to enable step-out/step-over/etc. prior to the first await yielding
590 /// in an async method. In that case, we don't know the actual TStateMachine type, so we're forced to
591 /// use IAsyncStateMachine instead.
593 [MethodImpl(MethodImplOptions.NoInlining)]
594 private Task<TResult> InitializeTaskAsStateMachineBox()
596 Debug.Assert(m_task == null);
597 return (m_task = AsyncMethodBuilderCore.TrackAsyncMethodCompletion ?
598 new DebugFinalizableAsyncStateMachineBox<IAsyncStateMachine>() :
599 new AsyncStateMachineBox<IAsyncStateMachine>());
603 /// Completes the <see cref="System.Threading.Tasks.Task{TResult}"/> in the
604 /// <see cref="System.Threading.Tasks.TaskStatus">RanToCompletion</see> state with the specified result.
606 /// <param name="result">The result to use to complete the task.</param>
607 /// <exception cref="System.InvalidOperationException">The task has already completed.</exception>
608 public void SetResult(TResult result)
610 // Get the currently stored task, which will be non-null if get_Task has already been accessed.
611 // If there isn't one, get a task and store it.
614 m_task = GetTaskForResult(result);
615 Debug.Assert(m_task != null, $"{nameof(GetTaskForResult)} should never return null");
619 // Slow path: complete the existing task.
620 SetExistingTaskResult(result);
624 /// <summary>Completes the already initialized task with the specified result.</summary>
625 /// <param name="result">The result to use to complete the task.</param>
626 private void SetExistingTaskResult(TResult result)
628 Debug.Assert(m_task != null, "Expected non-null task");
630 if (AsyncCausalityTracer.LoggingOn || System.Threading.Tasks.Task.s_asyncDebuggingEnabled)
632 LogExistingTaskCompletion();
635 if (!m_task.TrySetResult(result))
637 ThrowHelper.ThrowInvalidOperationException(ExceptionResource.TaskT_TransitionToFinal_AlreadyCompleted);
641 /// <summary>Handles logging for the successful completion of an operation.</summary>
642 private void LogExistingTaskCompletion()
644 Debug.Assert(m_task != null);
646 if (AsyncCausalityTracer.LoggingOn)
648 AsyncCausalityTracer.TraceOperationCompletion(CausalityTraceLevel.Required, m_task.Id, AsyncCausalityStatus.Completed);
651 // only log if we have a real task that was previously created
652 if (System.Threading.Tasks.Task.s_asyncDebuggingEnabled)
654 System.Threading.Tasks.Task.RemoveFromActiveTasks(m_task.Id);
659 /// Completes the builder by using either the supplied completed task, or by completing
660 /// the builder's previously accessed task using default(TResult).
662 /// <param name="completedTask">A task already completed with the value default(TResult).</param>
663 /// <exception cref="System.InvalidOperationException">The task has already completed.</exception>
664 internal void SetResult(Task<TResult> completedTask)
666 Debug.Assert(completedTask != null, "Expected non-null task");
667 Debug.Assert(completedTask.IsCompletedSuccessfully, "Expected a successfully completed task");
669 // Get the currently stored task, which will be non-null if get_Task has already been accessed.
670 // If there isn't one, store the supplied completed task.
673 m_task = completedTask;
677 // Otherwise, complete the task that's there.
678 SetExistingTaskResult(default(TResult));
683 /// Completes the <see cref="System.Threading.Tasks.Task{TResult}"/> in the
684 /// <see cref="System.Threading.Tasks.TaskStatus">Faulted</see> state with the specified exception.
686 /// <param name="exception">The <see cref="System.Exception"/> to use to fault the task.</param>
687 /// <exception cref="System.ArgumentNullException">The <paramref name="exception"/> argument is null (Nothing in Visual Basic).</exception>
688 /// <exception cref="System.InvalidOperationException">The task has already completed.</exception>
689 public void SetException(Exception exception)
691 if (exception == null)
693 ThrowHelper.ThrowArgumentNullException(ExceptionArgument.exception);
696 // Get the task, forcing initialization if it hasn't already been initialized.
697 Task<TResult> task = this.Task;
699 // If the exception represents cancellation, cancel the task. Otherwise, fault the task.
700 var oce = exception as OperationCanceledException;
701 bool successfullySet = oce != null ?
702 task.TrySetCanceled(oce.CancellationToken, oce) :
703 task.TrySetException(exception);
705 // Unlike with TaskCompletionSource, we do not need to spin here until _taskAndStateMachine is completed,
706 // since AsyncTaskMethodBuilder.SetException should not be immediately followed by any code
707 // that depends on the task having completely completed. Moreover, with correct usage,
708 // SetResult or SetException should only be called once, so the Try* methods should always
709 // return true, so no spinning would be necessary anyway (the spinning in TCS is only relevant
710 // if another thread completes the task first).
711 if (!successfullySet)
713 ThrowHelper.ThrowInvalidOperationException(ExceptionResource.TaskT_TransitionToFinal_AlreadyCompleted);
718 /// Called by the debugger to request notification when the first wait operation
719 /// (await, Wait, Result, etc.) on this builder's task completes.
721 /// <param name="enabled">
722 /// true to enable notification; false to disable a previously set notification.
725 /// This should only be invoked from within an asynchronous method,
726 /// and only by the debugger.
728 internal void SetNotificationForWaitCompletion(bool enabled)
730 // Get the task (forcing initialization if not already initialized), and set debug notification
731 (m_task ?? InitializeTaskAsStateMachineBox()).SetNotificationForWaitCompletion(enabled);
733 // NOTE: It's important that the debugger use builder.SetNotificationForWaitCompletion
734 // rather than builder.Task.SetNotificationForWaitCompletion. Even though the latter will
735 // lazily-initialize the task as well, it'll initialize it to a Task<T> (which is important
736 // to minimize size for cases where an ATMB is used directly by user code to avoid the
737 // allocation overhead of a TaskCompletionSource). If that's done prior to the first await,
738 // the GetMoveNextDelegate code, which needs an AsyncStateMachineBox, will end up creating
739 // a new box and overwriting the previously created task. That'll change the object identity
740 // of the task being used for wait completion notification, and no notification will
741 // ever arrive, breaking step-out behavior when stepping out before the first yielding await.
745 /// Gets an object that may be used to uniquely identify this builder to the debugger.
748 /// This property lazily instantiates the ID in a non-thread-safe manner.
749 /// It must only be used by the debugger and tracing purposes, and only in a single-threaded manner
750 /// when no other threads are in the middle of accessing this or other members that lazily initialize the task.
752 internal object ObjectIdForDebugger => m_task ?? InitializeTaskAsStateMachineBox();
755 /// Gets a task for the specified result. This will either
756 /// be a cached or new task, never null.
758 /// <param name="result">The result for which we need a task.</param>
759 /// <returns>The completed task containing the result.</returns>
760 [MethodImpl(MethodImplOptions.AggressiveInlining)] // method looks long, but for a given TResult it results in a relatively small amount of asm
761 internal static Task<TResult> GetTaskForResult(TResult result)
763 // The goal of this function is to be give back a cached task if possible,
764 // or to otherwise give back a new task. To give back a cached task,
765 // we need to be able to evaluate the incoming result value, and we need
766 // to avoid as much overhead as possible when doing so, as this function
767 // is invoked as part of the return path from every async method.
768 // Most tasks won't be cached, and thus we need the checks for those that are
769 // to be as close to free as possible. This requires some trickiness given the
770 // lack of generic specialization in .NET.
772 // Be very careful when modifying this code. It has been tuned
773 // to comply with patterns recognized by both 32-bit and 64-bit JITs.
774 // If changes are made here, be sure to look at the generated assembly, as
775 // small tweaks can have big consequences for what does and doesn't get optimized away.
777 // Note that this code only ever accesses a static field when it knows it'll
778 // find a cached value, since static fields (even if readonly and integral types)
779 // require special access helpers in this NGEN'd and domain-neutral.
781 if (null != (object)default(TResult)) // help the JIT avoid the value type branches for ref types
783 // Special case simple value types:
792 // As of .NET 4.5, the (Type)(object)result pattern used below
793 // is recognized and optimized by both 32-bit and 64-bit JITs.
795 // For Boolean, we cache all possible values.
796 if (typeof(TResult) == typeof(Boolean)) // only the relevant branches are kept for each value-type generic instantiation
798 Boolean value = (Boolean)(object)result;
799 Task<Boolean> task = value ? AsyncTaskCache.TrueTask : AsyncTaskCache.FalseTask;
800 return Unsafe.As<Task<TResult>>(task); // UnsafeCast avoids type check we know will succeed
802 // For Int32, we cache a range of common values, e.g. [-1,4).
803 else if (typeof(TResult) == typeof(Int32))
805 // Compare to constants to avoid static field access if outside of cached range.
806 // We compare to the upper bound first, as we're more likely to cache miss on the upper side than on the
807 // lower side, due to positive values being more common than negative as return values.
808 Int32 value = (Int32)(object)result;
809 if (value < AsyncTaskCache.EXCLUSIVE_INT32_MAX &&
810 value >= AsyncTaskCache.INCLUSIVE_INT32_MIN)
812 Task<Int32> task = AsyncTaskCache.Int32Tasks[value - AsyncTaskCache.INCLUSIVE_INT32_MIN];
813 return Unsafe.As<Task<TResult>>(task); // UnsafeCast avoids a type check we know will succeed
816 // For other known value types, we only special-case 0 / default(TResult).
818 (typeof(TResult) == typeof(UInt32) && default(UInt32) == (UInt32)(object)result) ||
819 (typeof(TResult) == typeof(Byte) && default(Byte) == (Byte)(object)result) ||
820 (typeof(TResult) == typeof(SByte) && default(SByte) == (SByte)(object)result) ||
821 (typeof(TResult) == typeof(Char) && default(Char) == (Char)(object)result) ||
822 (typeof(TResult) == typeof(Decimal) && default(Decimal) == (Decimal)(object)result) ||
823 (typeof(TResult) == typeof(Int64) && default(Int64) == (Int64)(object)result) ||
824 (typeof(TResult) == typeof(UInt64) && default(UInt64) == (UInt64)(object)result) ||
825 (typeof(TResult) == typeof(Int16) && default(Int16) == (Int16)(object)result) ||
826 (typeof(TResult) == typeof(UInt16) && default(UInt16) == (UInt16)(object)result) ||
827 (typeof(TResult) == typeof(IntPtr) && default(IntPtr) == (IntPtr)(object)result) ||
828 (typeof(TResult) == typeof(UIntPtr) && default(UIntPtr) == (UIntPtr)(object)result))
830 return s_defaultResultTask;
833 else if (result == null) // optimized away for value types
835 return s_defaultResultTask;
838 // No cached task is available. Manufacture a new one for this result.
839 return new Task<TResult>(result);
843 /// <summary>Provides a cache of closed generic tasks for async methods.</summary>
844 internal static class AsyncTaskCache
846 // All static members are initialized inline to ensure type is beforefieldinit
848 /// <summary>A cached Task{Boolean}.Result == true.</summary>
849 internal readonly static Task<Boolean> TrueTask = CreateCacheableTask(true);
850 /// <summary>A cached Task{Boolean}.Result == false.</summary>
851 internal readonly static Task<Boolean> FalseTask = CreateCacheableTask(false);
853 /// <summary>The cache of Task{Int32}.</summary>
854 internal readonly static Task<Int32>[] Int32Tasks = CreateInt32Tasks();
855 /// <summary>The minimum value, inclusive, for which we want a cached task.</summary>
856 internal const Int32 INCLUSIVE_INT32_MIN = -1;
857 /// <summary>The maximum value, exclusive, for which we want a cached task.</summary>
858 internal const Int32 EXCLUSIVE_INT32_MAX = 9;
859 /// <summary>Creates an array of cached tasks for the values in the range [INCLUSIVE_MIN,EXCLUSIVE_MAX).</summary>
860 private static Task<Int32>[] CreateInt32Tasks()
862 Debug.Assert(EXCLUSIVE_INT32_MAX >= INCLUSIVE_INT32_MIN, "Expected max to be at least min");
863 var tasks = new Task<Int32>[EXCLUSIVE_INT32_MAX - INCLUSIVE_INT32_MIN];
864 for (int i = 0; i < tasks.Length; i++)
866 tasks[i] = CreateCacheableTask(i + INCLUSIVE_INT32_MIN);
871 /// <summary>Creates a non-disposable task.</summary>
872 /// <typeparam name="TResult">Specifies the result type.</typeparam>
873 /// <param name="result">The result for the task.</param>
874 /// <returns>The cacheable task.</returns>
875 internal static Task<TResult> CreateCacheableTask<TResult>(TResult result) =>
876 new Task<TResult>(false, result, (TaskCreationOptions)InternalTaskOptions.DoNotDispose, default(CancellationToken));
880 /// An interface implemented by all <see cref="AsyncStateMachineBox{TStateMachine, TResult}"/> instances, regardless of generics.
882 internal interface IAsyncStateMachineBox : ITaskCompletionAction
885 /// Gets an action for moving forward the contained state machine.
886 /// This will lazily-allocate the delegate as needed.
888 Action MoveNextAction { get; }
890 /// <summary>Gets the state machine as a boxed object. This should only be used for debugging purposes.</summary>
891 IAsyncStateMachine GetStateMachineObject();
894 /// <summary>Shared helpers for manipulating state related to async state machines.</summary>
895 internal static class AsyncMethodBuilderCore // debugger depends on this exact name
897 /// <summary>Gets whether we should be tracking async method completions for eventing.</summary>
898 internal static bool TrackAsyncMethodCompletion
900 [MethodImpl(MethodImplOptions.AggressiveInlining)]
901 get => TplEtwProvider.Log.IsEnabled(EventLevel.Warning, TplEtwProvider.Keywords.AsyncMethod);
904 /// <summary>Gets a description of the state of the state machine object, suitable for debug purposes.</summary>
905 /// <param name="stateMachine">The state machine object.</param>
906 /// <returns>A description of the state machine.</returns>
907 internal static string GetAsyncStateMachineDescription(IAsyncStateMachine stateMachine)
909 Debug.Assert(stateMachine != null);
911 Type stateMachineType = stateMachine.GetType();
912 FieldInfo[] fields = stateMachineType.GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
914 var sb = new StringBuilder();
915 sb.AppendLine(stateMachineType.FullName);
916 foreach (FieldInfo fi in fields)
918 sb.AppendLine($" {fi.Name}: {fi.GetValue(stateMachine)}");
920 return sb.ToString();
923 internal static Action OutputAsyncCausalityEvents(Task task, Action continuation) =>
924 CreateContinuationWrapper(continuation, (innerContinuation, innerTask) =>
926 AsyncCausalityTracer.TraceSynchronousWorkStart(CausalityTraceLevel.Required, innerTask.Id, CausalitySynchronousWork.Execution);
927 innerContinuation.Invoke(); // Invoke the original continuation
928 AsyncCausalityTracer.TraceSynchronousWorkCompletion(CausalityTraceLevel.Required, CausalitySynchronousWork.Execution);
931 internal static Action CreateContinuationWrapper(Action continuation, Action<Action,Task> invokeAction, Task innerTask) =>
932 new ContinuationWrapper(continuation, invokeAction, innerTask).Invoke;
934 internal static Action TryGetStateMachineForDebugger(Action action) // debugger depends on this exact name/signature
936 object target = action.Target;
938 target is IAsyncStateMachineBox sm ? sm.GetStateMachineObject().MoveNext :
939 target is ContinuationWrapper cw ? TryGetStateMachineForDebugger(cw._continuation) :
943 internal static Task TryGetContinuationTask(Action continuation) =>
944 (continuation?.Target as ContinuationWrapper)?._innerTask;
946 /// <summary>Throws the exception on the ThreadPool.</summary>
947 /// <param name="exception">The exception to propagate.</param>
948 /// <param name="targetContext">The target context on which to propagate the exception. Null to use the ThreadPool.</param>
949 internal static void ThrowAsync(Exception exception, SynchronizationContext targetContext)
951 // Capture the exception into an ExceptionDispatchInfo so that its
952 // stack trace and Watson bucket info will be preserved
953 var edi = ExceptionDispatchInfo.Capture(exception);
955 // If the user supplied a SynchronizationContext...
956 if (targetContext != null)
960 // Post the throwing of the exception to that context, and return.
961 targetContext.Post(state => ((ExceptionDispatchInfo)state).Throw(), edi);
964 catch (Exception postException)
966 // If something goes horribly wrong in the Post, we'll
967 // propagate both exceptions on the ThreadPool
968 edi = ExceptionDispatchInfo.Capture(new AggregateException(exception, postException));
972 // If we have the new error reporting APIs, report this error. Otherwise, Propagate the exception(s) on the ThreadPool
973 #if FEATURE_COMINTEROP
974 if (!WindowsRuntimeMarshal.ReportUnhandledError(edi.SourceException))
975 #endif // FEATURE_COMINTEROP
977 ThreadPool.QueueUserWorkItem(state => ((ExceptionDispatchInfo)state).Throw(), edi);
982 /// Logically we pass just an Action (delegate) to a task for its action to 'ContinueWith' when it completes.
983 /// However debuggers and profilers need more information about what that action is. (In particular what
984 /// the action after that is and after that. To solve this problem we create a 'ContinuationWrapper
985 /// which when invoked just does the original action (the invoke action), but also remembers other information
986 /// (like the action after that (which is also a ContinuationWrapper and thus form a linked list).
987 // We also store that task if the action is associate with at task.
989 private sealed class ContinuationWrapper
991 private readonly Action<Action, Task> _invokeAction; // This wrapper is an action that wraps another action, this is that Action.
992 internal readonly Action _continuation; // This is continuation which will happen after m_invokeAction (and is probably a ContinuationWrapper)
993 internal readonly Task _innerTask; // If the continuation is logically going to invoke a task, this is that task (may be null)
995 internal ContinuationWrapper(Action continuation, Action<Action, Task> invokeAction, Task innerTask)
997 Debug.Assert(continuation != null, "Expected non-null continuation");
998 Debug.Assert(invokeAction != null, "Expected non-null continuation");
1000 _invokeAction = invokeAction;
1001 _continuation = continuation;
1002 _innerTask = innerTask ?? TryGetContinuationTask(continuation); // if we don't have a task, see if our continuation is a wrapper and use that.
1005 internal void Invoke() => _invokeAction(_continuation, _innerTask);