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 public void Start<TStateMachine>(ref TStateMachine stateMachine) where TStateMachine : IAsyncStateMachine =>
53 _builder.Start(ref stateMachine);
55 /// <summary>Associates the builder with the state machine it represents.</summary>
56 /// <param name="stateMachine">The heap-allocated state machine object.</param>
57 /// <exception cref="System.ArgumentNullException">The <paramref name="stateMachine"/> argument was null (Nothing in Visual Basic).</exception>
58 /// <exception cref="System.InvalidOperationException">The builder is incorrectly initialized.</exception>
59 public void SetStateMachine(IAsyncStateMachine stateMachine) =>
60 _builder.SetStateMachine(stateMachine);
63 /// Schedules the specified state machine to be pushed forward when the specified awaiter completes.
65 /// <typeparam name="TAwaiter">Specifies the type of the awaiter.</typeparam>
66 /// <typeparam name="TStateMachine">Specifies the type of the state machine.</typeparam>
67 /// <param name="awaiter">The awaiter.</param>
68 /// <param name="stateMachine">The state machine.</param>
69 public void AwaitOnCompleted<TAwaiter, TStateMachine>(
70 ref TAwaiter awaiter, ref TStateMachine stateMachine)
71 where TAwaiter : INotifyCompletion
72 where TStateMachine : IAsyncStateMachine =>
73 _builder.AwaitOnCompleted(ref awaiter, ref stateMachine);
76 /// Schedules the specified state machine to be pushed forward when the specified awaiter completes.
78 /// <typeparam name="TAwaiter">Specifies the type of the awaiter.</typeparam>
79 /// <typeparam name="TStateMachine">Specifies the type of the state machine.</typeparam>
80 /// <param name="awaiter">The awaiter.</param>
81 /// <param name="stateMachine">The state machine.</param>
82 public void AwaitUnsafeOnCompleted<TAwaiter, TStateMachine>(
83 ref TAwaiter awaiter, ref TStateMachine stateMachine)
84 where TAwaiter : ICriticalNotifyCompletion
85 where TStateMachine : IAsyncStateMachine =>
86 _builder.AwaitUnsafeOnCompleted(ref awaiter, ref stateMachine);
88 /// <summary>Completes the method builder successfully.</summary>
89 public void SetResult()
91 if (AsyncCausalityTracer.LoggingOn)
93 AsyncCausalityTracer.TraceOperationCompletion(CausalityTraceLevel.Required, this.Task.Id, AsyncCausalityStatus.Completed);
96 // Mark the builder as completed. As this is a void-returning method, this mostly
97 // doesn't matter, but it can affect things like debug events related to finalization.
100 if (_synchronizationContext != null)
102 NotifySynchronizationContextOfCompletion();
106 /// <summary>Faults the method builder with an exception.</summary>
107 /// <param name="exception">The exception that is the cause of this fault.</param>
108 /// <exception cref="System.ArgumentNullException">The <paramref name="exception"/> argument is null (Nothing in Visual Basic).</exception>
109 /// <exception cref="System.InvalidOperationException">The builder is not initialized.</exception>
110 public void SetException(Exception exception)
112 if (exception == null)
114 ThrowHelper.ThrowArgumentNullException(ExceptionArgument.exception);
117 if (AsyncCausalityTracer.LoggingOn)
119 AsyncCausalityTracer.TraceOperationCompletion(CausalityTraceLevel.Required, this.Task.Id, AsyncCausalityStatus.Error);
122 if (_synchronizationContext != null)
124 // If we captured a synchronization context, Post the throwing of the exception to it
125 // and decrement its outstanding operation count.
128 AsyncMethodBuilderCore.ThrowAsync(exception, targetContext: _synchronizationContext);
132 NotifySynchronizationContextOfCompletion();
137 // Otherwise, queue the exception to be thrown on the ThreadPool. This will
138 // result in a crash unless legacy exception behavior is enabled by a config
139 // file or a CLR host.
140 AsyncMethodBuilderCore.ThrowAsync(exception, targetContext: null);
143 // The exception was propagated already; we don't need or want to fault the builder, just mark it as completed.
144 _builder.SetResult();
147 /// <summary>Notifies the current synchronization context that the operation completed.</summary>
148 private void NotifySynchronizationContextOfCompletion()
150 Debug.Assert(_synchronizationContext != null, "Must only be used with a non-null context.");
153 _synchronizationContext.OperationCompleted();
155 catch (Exception exc)
157 // If the interaction with the SynchronizationContext goes awry,
158 // fall back to propagating on the ThreadPool.
159 AsyncMethodBuilderCore.ThrowAsync(exc, targetContext: null);
163 /// <summary>Lazily instantiate the Task in a non-thread-safe manner.</summary>
164 private Task Task => _builder.Task;
167 /// Gets an object that may be used to uniquely identify this builder to the debugger.
170 /// This property lazily instantiates the ID in a non-thread-safe manner.
171 /// It must only be used by the debugger and AsyncCausalityTracer in a single-threaded manner.
173 internal object ObjectIdForDebugger => _builder.ObjectIdForDebugger;
177 /// Provides a builder for asynchronous methods that return <see cref="System.Threading.Tasks.Task"/>.
178 /// This type is intended for compiler use only.
181 /// AsyncTaskMethodBuilder is a value type, and thus it is copied by value.
182 /// Prior to being copied, one of its Task, SetResult, or SetException members must be accessed,
183 /// or else the copies may end up building distinct Task instances.
185 public struct AsyncTaskMethodBuilder
187 /// <summary>A cached VoidTaskResult task used for builders that complete synchronously.</summary>
188 private readonly static Task<VoidTaskResult> s_cachedCompleted = AsyncTaskMethodBuilder<VoidTaskResult>.s_defaultResultTask;
190 /// <summary>The generic builder object to which this non-generic instance delegates.</summary>
191 private AsyncTaskMethodBuilder<VoidTaskResult> m_builder; // mutable struct: must not be readonly. Debugger depends on the exact name of this field.
193 /// <summary>Initializes a new <see cref="AsyncTaskMethodBuilder"/>.</summary>
194 /// <returns>The initialized <see cref="AsyncTaskMethodBuilder"/>.</returns>
195 public static AsyncTaskMethodBuilder Create() => default(AsyncTaskMethodBuilder);
197 /// <summary>Initiates the builder's execution with the associated state machine.</summary>
198 /// <typeparam name="TStateMachine">Specifies the type of the state machine.</typeparam>
199 /// <param name="stateMachine">The state machine instance, passed by reference.</param>
200 [DebuggerStepThrough]
201 public void Start<TStateMachine>(ref TStateMachine stateMachine) where TStateMachine : IAsyncStateMachine =>
202 m_builder.Start(ref stateMachine);
204 /// <summary>Associates the builder with the state machine it represents.</summary>
205 /// <param name="stateMachine">The heap-allocated state machine object.</param>
206 /// <exception cref="System.ArgumentNullException">The <paramref name="stateMachine"/> argument was null (Nothing in Visual Basic).</exception>
207 /// <exception cref="System.InvalidOperationException">The builder is incorrectly initialized.</exception>
208 public void SetStateMachine(IAsyncStateMachine stateMachine) =>
209 m_builder.SetStateMachine(stateMachine);
212 /// Schedules the specified state machine to be pushed forward when the specified awaiter completes.
214 /// <typeparam name="TAwaiter">Specifies the type of the awaiter.</typeparam>
215 /// <typeparam name="TStateMachine">Specifies the type of the state machine.</typeparam>
216 /// <param name="awaiter">The awaiter.</param>
217 /// <param name="stateMachine">The state machine.</param>
218 public void AwaitOnCompleted<TAwaiter, TStateMachine>(
219 ref TAwaiter awaiter, ref TStateMachine stateMachine)
220 where TAwaiter : INotifyCompletion
221 where TStateMachine : IAsyncStateMachine =>
222 m_builder.AwaitOnCompleted(ref awaiter, ref stateMachine);
225 /// Schedules the specified state machine to be pushed forward when the specified awaiter completes.
227 /// <typeparam name="TAwaiter">Specifies the type of the awaiter.</typeparam>
228 /// <typeparam name="TStateMachine">Specifies the type of the state machine.</typeparam>
229 /// <param name="awaiter">The awaiter.</param>
230 /// <param name="stateMachine">The state machine.</param>
231 public void AwaitUnsafeOnCompleted<TAwaiter, TStateMachine>(
232 ref TAwaiter awaiter, ref TStateMachine stateMachine)
233 where TAwaiter : ICriticalNotifyCompletion
234 where TStateMachine : IAsyncStateMachine =>
235 m_builder.AwaitUnsafeOnCompleted(ref awaiter, ref stateMachine);
237 /// <summary>Gets the <see cref="System.Threading.Tasks.Task"/> for this builder.</summary>
238 /// <returns>The <see cref="System.Threading.Tasks.Task"/> representing the builder's asynchronous operation.</returns>
239 /// <exception cref="System.InvalidOperationException">The builder is not initialized.</exception>
242 [MethodImpl(MethodImplOptions.AggressiveInlining)]
243 get => m_builder.Task;
247 /// Completes the <see cref="System.Threading.Tasks.Task"/> in the
248 /// <see cref="System.Threading.Tasks.TaskStatus">RanToCompletion</see> state.
250 /// <exception cref="System.InvalidOperationException">The builder is not initialized.</exception>
251 /// <exception cref="System.InvalidOperationException">The task has already completed.</exception>
252 public void SetResult() => m_builder.SetResult(s_cachedCompleted); // Using s_cachedCompleted is faster than using s_defaultResultTask.
255 /// Completes the <see cref="System.Threading.Tasks.Task"/> in the
256 /// <see cref="System.Threading.Tasks.TaskStatus">Faulted</see> state with the specified exception.
258 /// <param name="exception">The <see cref="System.Exception"/> to use to fault the task.</param>
259 /// <exception cref="System.ArgumentNullException">The <paramref name="exception"/> argument is null (Nothing in Visual Basic).</exception>
260 /// <exception cref="System.InvalidOperationException">The builder is not initialized.</exception>
261 /// <exception cref="System.InvalidOperationException">The task has already completed.</exception>
262 public void SetException(Exception exception) => m_builder.SetException(exception);
265 /// Called by the debugger to request notification when the first wait operation
266 /// (await, Wait, Result, etc.) on this builder's task completes.
268 /// <param name="enabled">
269 /// true to enable notification; false to disable a previously set notification.
271 internal void SetNotificationForWaitCompletion(bool enabled) => m_builder.SetNotificationForWaitCompletion(enabled);
274 /// Gets an object that may be used to uniquely identify this builder to the debugger.
277 /// This property lazily instantiates the ID in a non-thread-safe manner.
278 /// It must only be used by the debugger and tracing purposes, and only in a single-threaded manner
279 /// when no other threads are in the middle of accessing this property or this.Task.
281 internal object ObjectIdForDebugger => m_builder.ObjectIdForDebugger;
285 /// Provides a builder for asynchronous methods that return <see cref="System.Threading.Tasks.Task{TResult}"/>.
286 /// This type is intended for compiler use only.
289 /// AsyncTaskMethodBuilder{TResult} is a value type, and thus it is copied by value.
290 /// Prior to being copied, one of its Task, SetResult, or SetException members must be accessed,
291 /// or else the copies may end up building distinct Task instances.
293 public struct AsyncTaskMethodBuilder<TResult>
295 /// <summary>A cached task for default(TResult).</summary>
296 internal readonly static Task<TResult> s_defaultResultTask = AsyncTaskCache.CreateCacheableTask(default(TResult));
298 /// <summary>The lazily-initialized built task.</summary>
299 private Task<TResult> m_task; // lazily-initialized: must not be readonly. Debugger depends on the exact name of this field.
301 /// <summary>Initializes a new <see cref="AsyncTaskMethodBuilder"/>.</summary>
302 /// <returns>The initialized <see cref="AsyncTaskMethodBuilder"/>.</returns>
303 public static AsyncTaskMethodBuilder<TResult> Create()
305 return default(AsyncTaskMethodBuilder<TResult>);
306 // NOTE: If this method is ever updated to perform more initialization,
307 // other Create methods like AsyncTaskMethodBuilder.Create and
308 // AsyncValueTaskMethodBuilder.Create must be updated to call this.
311 /// <summary>Initiates the builder's execution with the associated state machine.</summary>
312 /// <typeparam name="TStateMachine">Specifies the type of the state machine.</typeparam>
313 /// <param name="stateMachine">The state machine instance, passed by reference.</param>
314 [DebuggerStepThrough]
315 public void Start<TStateMachine>(ref TStateMachine stateMachine) where TStateMachine : IAsyncStateMachine
317 if (stateMachine == null) // TStateMachines are generally non-nullable value types, so this check will be elided
319 ThrowHelper.ThrowArgumentNullException(ExceptionArgument.stateMachine);
322 // Run the MoveNext method within a copy-on-write ExecutionContext scope.
323 // This allows us to undo any ExecutionContext changes made in MoveNext,
324 // so that they won't "leak" out of the first await.
326 Thread currentThread = Thread.CurrentThread;
327 ExecutionContextSwitcher ecs = default(ExecutionContextSwitcher);
330 ExecutionContext.EstablishCopyOnWriteScope(currentThread, ref ecs);
331 stateMachine.MoveNext();
335 ecs.Undo(currentThread);
339 /// <summary>Associates the builder with the state machine it represents.</summary>
340 /// <param name="stateMachine">The heap-allocated state machine object.</param>
341 /// <exception cref="System.ArgumentNullException">The <paramref name="stateMachine"/> argument was null (Nothing in Visual Basic).</exception>
342 /// <exception cref="System.InvalidOperationException">The builder is incorrectly initialized.</exception>
343 public void SetStateMachine(IAsyncStateMachine stateMachine)
345 if (stateMachine == null)
347 ThrowHelper.ThrowArgumentNullException(ExceptionArgument.stateMachine);
352 ThrowHelper.ThrowInvalidOperationException(ExceptionResource.AsyncMethodBuilder_InstanceNotInitialized);
355 // SetStateMachine was originally needed in order to store the boxed state machine reference into
356 // the boxed copy. Now that a normal box is no longer used, SetStateMachine is also legacy. We need not
357 // do anything here, and thus assert to ensure we're not calling this from our own implementations.
358 Debug.Fail("SetStateMachine should not be used.");
362 /// Schedules the specified state machine to be pushed forward when the specified awaiter completes.
364 /// <typeparam name="TAwaiter">Specifies the type of the awaiter.</typeparam>
365 /// <typeparam name="TStateMachine">Specifies the type of the state machine.</typeparam>
366 /// <param name="awaiter">The awaiter.</param>
367 /// <param name="stateMachine">The state machine.</param>
368 public void AwaitOnCompleted<TAwaiter, TStateMachine>(
369 ref TAwaiter awaiter, ref TStateMachine stateMachine)
370 where TAwaiter : INotifyCompletion
371 where TStateMachine : IAsyncStateMachine
375 awaiter.OnCompleted(GetStateMachineBox(ref stateMachine).MoveNextAction);
379 AsyncMethodBuilderCore.ThrowAsync(e, targetContext: null);
384 /// Schedules the specified state machine to be pushed forward when the specified awaiter completes.
386 /// <typeparam name="TAwaiter">Specifies the type of the awaiter.</typeparam>
387 /// <typeparam name="TStateMachine">Specifies the type of the state machine.</typeparam>
388 /// <param name="awaiter">The awaiter.</param>
389 /// <param name="stateMachine">The state machine.</param>
390 public void AwaitUnsafeOnCompleted<TAwaiter, TStateMachine>(
391 ref TAwaiter awaiter, ref TStateMachine stateMachine)
392 where TAwaiter : ICriticalNotifyCompletion
393 where TStateMachine : IAsyncStateMachine
395 IAsyncStateMachineBox box = GetStateMachineBox(ref stateMachine);
397 // The null tests here ensure that the jit can optimize away the interface
398 // tests when TAwaiter is is a ref type.
399 if ((null != (object)default(TAwaiter)) && (awaiter is ITaskAwaiter))
401 ref TaskAwaiter ta = ref Unsafe.As<TAwaiter, TaskAwaiter>(ref awaiter); // relies on TaskAwaiter/TaskAwaiter<T> having the same layout
402 TaskAwaiter.UnsafeOnCompletedInternal(ta.m_task, box, continueOnCapturedContext: true);
404 else if ((null != (object)default(TAwaiter)) && (awaiter is IConfiguredTaskAwaiter))
406 ref ConfiguredTaskAwaitable.ConfiguredTaskAwaiter ta = ref Unsafe.As<TAwaiter, ConfiguredTaskAwaitable.ConfiguredTaskAwaiter>(ref awaiter);
407 TaskAwaiter.UnsafeOnCompletedInternal(ta.m_task, box, ta.m_continueOnCapturedContext);
409 else if ((null != (object)default(TAwaiter)) && (awaiter is IValueTaskAwaiter))
411 Task t = ((IValueTaskAwaiter)awaiter).GetTask();
412 TaskAwaiter.UnsafeOnCompletedInternal(t, box, continueOnCapturedContext: true);
414 else if ((null != (object)default(TAwaiter)) && (awaiter is IConfiguredValueTaskAwaiter))
416 Task t = ((IConfiguredValueTaskAwaiter)awaiter).GetTask(out bool continueOnCapturedContext);
417 TaskAwaiter.UnsafeOnCompletedInternal(t, box, continueOnCapturedContext);
419 // The awaiter isn't specially known. Fall back to doing a normal await.
424 awaiter.UnsafeOnCompleted(box.MoveNextAction);
428 AsyncMethodBuilderCore.ThrowAsync(e, targetContext: null);
433 /// <summary>Gets the "boxed" state machine object.</summary>
434 /// <typeparam name="TStateMachine">Specifies the type of the async state machine.</typeparam>
435 /// <param name="stateMachine">The state machine.</param>
436 /// <returns>The "boxed" state machine.</returns>
437 private IAsyncStateMachineBox GetStateMachineBox<TStateMachine>(
438 ref TStateMachine stateMachine)
439 where TStateMachine : IAsyncStateMachine
441 ExecutionContext currentContext = ExecutionContext.Capture();
443 // Check first for the most common case: not the first yield in an async method.
444 // In this case, the first yield will have already "boxed" the state machine in
445 // a strongly-typed manner into an AsyncStateMachineBox. It will already contain
446 // the state machine as well as a MoveNextDelegate and a context. The only thing
447 // we might need to do is update the context if that's changed since it was stored.
448 if (m_task is AsyncStateMachineBox<TStateMachine> stronglyTypedBox)
450 if (stronglyTypedBox.Context != currentContext)
452 stronglyTypedBox.Context = currentContext;
454 return stronglyTypedBox;
457 // The least common case: we have a weakly-typed boxed. This results if the debugger
458 // or some other use of reflection accesses a property like ObjectIdForDebugger or a
459 // method like SetNotificationForWaitCompletion prior to the first await happening. In
460 // such situations, we need to get an object to represent the builder, but we don't yet
461 // know the type of the state machine, and thus can't use TStateMachine. Instead, we
462 // use the IAsyncStateMachine interface, which all TStateMachines implement. This will
463 // result in a boxing allocation when storing the TStateMachine if it's a struct, but
464 // this only happens in active debugging scenarios where such performance impact doesn't
466 if (m_task is AsyncStateMachineBox<IAsyncStateMachine> weaklyTypedBox)
468 // If this is the first await, we won't yet have a state machine, so store it.
469 if (weaklyTypedBox.StateMachine == null)
471 Debugger.NotifyOfCrossThreadDependency(); // same explanation as with usage below
472 weaklyTypedBox.StateMachine = stateMachine;
475 // Update the context. This only happens with a debugger, so no need to spend
476 // extra IL checking for equality before doing the assignment.
477 weaklyTypedBox.Context = currentContext;
478 return weaklyTypedBox;
481 // Alert a listening debugger that we can't make forward progress unless it slips threads.
482 // If we don't do this, and a method that uses "await foo;" is invoked through funceval,
483 // we could end up hooking up a callback to push forward the async method's state machine,
484 // the debugger would then abort the funceval after it takes too long, and then continuing
485 // execution could result in another callback being hooked up. At that point we have
486 // multiple callbacks registered to push the state machine, which could result in bad behavior.
487 Debugger.NotifyOfCrossThreadDependency();
489 // At this point, m_task should really be null, in which case we want to create the box.
490 // However, in a variety of debugger-related (erroneous) situations, it might be non-null,
491 // e.g. if the Task property is examined in a Watch window, forcing it to be lazily-intialized
492 // as a Task<TResult> rather than as an AsyncStateMachineBox. The worst that happens in such
493 // cases is we lose the ability to properly step in the debugger, as the debugger uses that
494 // object's identity to track this specific builder/state machine. As such, we proceed to
495 // overwrite whatever's there anyway, even if it's non-null.
496 var box = AsyncMethodBuilderCore.TrackAsyncMethodCompletion ?
497 new DebugFinalizableAsyncStateMachineBox<TStateMachine>() :
498 new AsyncStateMachineBox<TStateMachine>();
499 m_task = box; // important: this must be done before storing stateMachine into box.StateMachine!
500 box.StateMachine = stateMachine;
501 box.Context = currentContext;
506 /// Provides an async state machine box with a finalizer that will fire an EventSource
507 /// event about the state machine if it's being finalized without having been completed.
509 /// <typeparam name="TStateMachine">Specifies the type of the state machine.</typeparam>
510 private sealed class DebugFinalizableAsyncStateMachineBox<TStateMachine> :
511 AsyncStateMachineBox<TStateMachine>
512 where TStateMachine : IAsyncStateMachine
514 ~DebugFinalizableAsyncStateMachineBox()
516 // If the state machine is being finalized, something went wrong during its processing,
517 // e.g. it awaited something that got collected without itself having been completed.
518 // Fire an event with details about the state machine to help with debugging.
519 if (!IsCompleted) // double-check it's not completed, just to help minimize false positives
521 TplEtwProvider.Log.IncompleteAsyncMethod(this);
526 /// <summary>A strongly-typed box for Task-based async state machines.</summary>
527 /// <typeparam name="TStateMachine">Specifies the type of the state machine.</typeparam>
528 /// <typeparam name="TResult">Specifies the type of the Task's result.</typeparam>
529 private class AsyncStateMachineBox<TStateMachine> :
530 Task<TResult>, IAsyncStateMachineBox
531 where TStateMachine : IAsyncStateMachine
533 /// <summary>Delegate used to invoke on an ExecutionContext when passed an instance of this box type.</summary>
534 private static readonly ContextCallback s_callback = s => ((AsyncStateMachineBox<TStateMachine>)s).StateMachine.MoveNext();
536 /// <summary>A delegate to the <see cref="MoveNext"/> method.</summary>
537 private Action _moveNextAction;
538 /// <summary>The state machine itself.</summary>
539 public TStateMachine StateMachine; // mutable struct; do not make this readonly
540 /// <summary>Captured ExecutionContext with which to invoke <see cref="MoveNextAction"/>; may be null.</summary>
541 public ExecutionContext Context;
543 /// <summary>A delegate to the <see cref="MoveNext"/> method.</summary>
544 public Action MoveNextAction =>
546 (_moveNextAction = AsyncCausalityTracer.LoggingOn ? AsyncMethodBuilderCore.OutputAsyncCausalityEvents(this, new Action(MoveNext)) : new Action(MoveNext));
548 /// <summary>Calls MoveNext on <see cref="StateMachine"/></summary>
549 public void MoveNext()
553 StateMachine.MoveNext();
557 ExecutionContext.Run(Context, s_callback, this);
560 // In case this is a state machine box with a finalizer, suppress its finalization
561 // if it's now complete. We only need the finalizer to run if the box is collected
562 // without having been completed.
563 if (IsCompleted && AsyncMethodBuilderCore.TrackAsyncMethodCompletion)
565 GC.SuppressFinalize(this);
570 /// Calls MoveNext on <see cref="StateMachine"/>. Implements ITaskCompletionAction.Invoke so
571 /// that the state machine object may be queued directly as a continuation into a Task's
572 /// continuation slot/list.
574 /// <param name="completedTask">The completing task that caused this method to be invoked, if there was one.</param>
575 void ITaskCompletionAction.Invoke(Task completedTask) => MoveNext();
577 /// <summary>Signals to Task's continuation logic that <see cref="Invoke"/> runs arbitrary user code via MoveNext.</summary>
578 bool ITaskCompletionAction.InvokeMayRunArbitraryCode => true;
580 /// <summary>Gets the state machine as a boxed object. This should only be used for debugging purposes.</summary>
581 IAsyncStateMachine IAsyncStateMachineBox.GetStateMachineObject() => StateMachine; // likely boxes, only use for debugging
584 /// <summary>Gets the <see cref="System.Threading.Tasks.Task{TResult}"/> for this builder.</summary>
585 /// <returns>The <see cref="System.Threading.Tasks.Task{TResult}"/> representing the builder's asynchronous operation.</returns>
586 public Task<TResult> Task
588 [MethodImpl(MethodImplOptions.AggressiveInlining)]
589 get => m_task ?? InitializeTaskAsPromise();
593 /// Initializes the task, which must not yet be initialized. Used only when the Task is being forced into
594 /// existence when no state machine is needed, e.g. when the builder is being synchronously completed with
595 /// an exception, when the builder is being used out of the context of an async method, etc.
597 [MethodImpl(MethodImplOptions.NoInlining)]
598 private Task<TResult> InitializeTaskAsPromise()
600 Debug.Assert(m_task == null);
601 return (m_task = new Task<TResult>());
605 /// Initializes the task, which must not yet be initialized. Used only when the Task is being forced into
606 /// existence due to the debugger trying to enable step-out/step-over/etc. prior to the first await yielding
607 /// in an async method. In that case, we don't know the actual TStateMachine type, so we're forced to
608 /// use IAsyncStateMachine instead.
610 [MethodImpl(MethodImplOptions.NoInlining)]
611 private Task<TResult> InitializeTaskAsStateMachineBox()
613 Debug.Assert(m_task == null);
614 return (m_task = AsyncMethodBuilderCore.TrackAsyncMethodCompletion ?
615 new DebugFinalizableAsyncStateMachineBox<IAsyncStateMachine>() :
616 new AsyncStateMachineBox<IAsyncStateMachine>());
620 /// Completes the <see cref="System.Threading.Tasks.Task{TResult}"/> in the
621 /// <see cref="System.Threading.Tasks.TaskStatus">RanToCompletion</see> state with the specified result.
623 /// <param name="result">The result to use to complete the task.</param>
624 /// <exception cref="System.InvalidOperationException">The task has already completed.</exception>
625 public void SetResult(TResult result)
627 // Get the currently stored task, which will be non-null if get_Task has already been accessed.
628 // If there isn't one, get a task and store it.
631 m_task = GetTaskForResult(result);
632 Debug.Assert(m_task != null, $"{nameof(GetTaskForResult)} should never return null");
636 // Slow path: complete the existing task.
637 SetExistingTaskResult(result);
641 /// <summary>Completes the already initialized task with the specified result.</summary>
642 /// <param name="result">The result to use to complete the task.</param>
643 private void SetExistingTaskResult(TResult result)
645 Debug.Assert(m_task != null, "Expected non-null task");
647 if (AsyncCausalityTracer.LoggingOn || System.Threading.Tasks.Task.s_asyncDebuggingEnabled)
649 LogExistingTaskCompletion();
652 if (!m_task.TrySetResult(result))
654 ThrowHelper.ThrowInvalidOperationException(ExceptionResource.TaskT_TransitionToFinal_AlreadyCompleted);
658 /// <summary>Handles logging for the successful completion of an operation.</summary>
659 private void LogExistingTaskCompletion()
661 Debug.Assert(m_task != null);
663 if (AsyncCausalityTracer.LoggingOn)
665 AsyncCausalityTracer.TraceOperationCompletion(CausalityTraceLevel.Required, m_task.Id, AsyncCausalityStatus.Completed);
668 // only log if we have a real task that was previously created
669 if (System.Threading.Tasks.Task.s_asyncDebuggingEnabled)
671 System.Threading.Tasks.Task.RemoveFromActiveTasks(m_task.Id);
676 /// Completes the builder by using either the supplied completed task, or by completing
677 /// the builder's previously accessed task using default(TResult).
679 /// <param name="completedTask">A task already completed with the value default(TResult).</param>
680 /// <exception cref="System.InvalidOperationException">The task has already completed.</exception>
681 internal void SetResult(Task<TResult> completedTask)
683 Debug.Assert(completedTask != null, "Expected non-null task");
684 Debug.Assert(completedTask.IsCompletedSuccessfully, "Expected a successfully completed task");
686 // Get the currently stored task, which will be non-null if get_Task has already been accessed.
687 // If there isn't one, store the supplied completed task.
690 m_task = completedTask;
694 // Otherwise, complete the task that's there.
695 SetExistingTaskResult(default(TResult));
700 /// Completes the <see cref="System.Threading.Tasks.Task{TResult}"/> in the
701 /// <see cref="System.Threading.Tasks.TaskStatus">Faulted</see> state with the specified exception.
703 /// <param name="exception">The <see cref="System.Exception"/> to use to fault the task.</param>
704 /// <exception cref="System.ArgumentNullException">The <paramref name="exception"/> argument is null (Nothing in Visual Basic).</exception>
705 /// <exception cref="System.InvalidOperationException">The task has already completed.</exception>
706 public void SetException(Exception exception)
708 if (exception == null)
710 ThrowHelper.ThrowArgumentNullException(ExceptionArgument.exception);
713 // Get the task, forcing initialization if it hasn't already been initialized.
714 Task<TResult> task = this.Task;
716 // If the exception represents cancellation, cancel the task. Otherwise, fault the task.
717 var oce = exception as OperationCanceledException;
718 bool successfullySet = oce != null ?
719 task.TrySetCanceled(oce.CancellationToken, oce) :
720 task.TrySetException(exception);
722 // Unlike with TaskCompletionSource, we do not need to spin here until _taskAndStateMachine is completed,
723 // since AsyncTaskMethodBuilder.SetException should not be immediately followed by any code
724 // that depends on the task having completely completed. Moreover, with correct usage,
725 // SetResult or SetException should only be called once, so the Try* methods should always
726 // return true, so no spinning would be necessary anyway (the spinning in TCS is only relevant
727 // if another thread completes the task first).
728 if (!successfullySet)
730 ThrowHelper.ThrowInvalidOperationException(ExceptionResource.TaskT_TransitionToFinal_AlreadyCompleted);
735 /// Called by the debugger to request notification when the first wait operation
736 /// (await, Wait, Result, etc.) on this builder's task completes.
738 /// <param name="enabled">
739 /// true to enable notification; false to disable a previously set notification.
742 /// This should only be invoked from within an asynchronous method,
743 /// and only by the debugger.
745 internal void SetNotificationForWaitCompletion(bool enabled)
747 // Get the task (forcing initialization if not already initialized), and set debug notification
748 (m_task ?? InitializeTaskAsStateMachineBox()).SetNotificationForWaitCompletion(enabled);
750 // NOTE: It's important that the debugger use builder.SetNotificationForWaitCompletion
751 // rather than builder.Task.SetNotificationForWaitCompletion. Even though the latter will
752 // lazily-initialize the task as well, it'll initialize it to a Task<T> (which is important
753 // to minimize size for cases where an ATMB is used directly by user code to avoid the
754 // allocation overhead of a TaskCompletionSource). If that's done prior to the first await,
755 // the GetMoveNextDelegate code, which needs an AsyncStateMachineBox, will end up creating
756 // a new box and overwriting the previously created task. That'll change the object identity
757 // of the task being used for wait completion notification, and no notification will
758 // ever arrive, breaking step-out behavior when stepping out before the first yielding await.
762 /// Gets an object that may be used to uniquely identify this builder to the debugger.
765 /// This property lazily instantiates the ID in a non-thread-safe manner.
766 /// It must only be used by the debugger and tracing purposes, and only in a single-threaded manner
767 /// when no other threads are in the middle of accessing this or other members that lazily initialize the task.
769 internal object ObjectIdForDebugger => m_task ?? InitializeTaskAsStateMachineBox();
772 /// Gets a task for the specified result. This will either
773 /// be a cached or new task, never null.
775 /// <param name="result">The result for which we need a task.</param>
776 /// <returns>The completed task containing the result.</returns>
777 [MethodImpl(MethodImplOptions.AggressiveInlining)] // method looks long, but for a given TResult it results in a relatively small amount of asm
778 internal static Task<TResult> GetTaskForResult(TResult result)
780 // The goal of this function is to be give back a cached task if possible,
781 // or to otherwise give back a new task. To give back a cached task,
782 // we need to be able to evaluate the incoming result value, and we need
783 // to avoid as much overhead as possible when doing so, as this function
784 // is invoked as part of the return path from every async method.
785 // Most tasks won't be cached, and thus we need the checks for those that are
786 // to be as close to free as possible. This requires some trickiness given the
787 // lack of generic specialization in .NET.
789 // Be very careful when modifying this code. It has been tuned
790 // to comply with patterns recognized by both 32-bit and 64-bit JITs.
791 // If changes are made here, be sure to look at the generated assembly, as
792 // small tweaks can have big consequences for what does and doesn't get optimized away.
794 // Note that this code only ever accesses a static field when it knows it'll
795 // find a cached value, since static fields (even if readonly and integral types)
796 // require special access helpers in this NGEN'd and domain-neutral.
798 if (null != (object)default(TResult)) // help the JIT avoid the value type branches for ref types
800 // Special case simple value types:
809 // As of .NET 4.5, the (Type)(object)result pattern used below
810 // is recognized and optimized by both 32-bit and 64-bit JITs.
812 // For Boolean, we cache all possible values.
813 if (typeof(TResult) == typeof(Boolean)) // only the relevant branches are kept for each value-type generic instantiation
815 Boolean value = (Boolean)(object)result;
816 Task<Boolean> task = value ? AsyncTaskCache.TrueTask : AsyncTaskCache.FalseTask;
817 return Unsafe.As<Task<TResult>>(task); // UnsafeCast avoids type check we know will succeed
819 // For Int32, we cache a range of common values, e.g. [-1,4).
820 else if (typeof(TResult) == typeof(Int32))
822 // Compare to constants to avoid static field access if outside of cached range.
823 // We compare to the upper bound first, as we're more likely to cache miss on the upper side than on the
824 // lower side, due to positive values being more common than negative as return values.
825 Int32 value = (Int32)(object)result;
826 if (value < AsyncTaskCache.EXCLUSIVE_INT32_MAX &&
827 value >= AsyncTaskCache.INCLUSIVE_INT32_MIN)
829 Task<Int32> task = AsyncTaskCache.Int32Tasks[value - AsyncTaskCache.INCLUSIVE_INT32_MIN];
830 return Unsafe.As<Task<TResult>>(task); // UnsafeCast avoids a type check we know will succeed
833 // For other known value types, we only special-case 0 / default(TResult).
835 (typeof(TResult) == typeof(UInt32) && default(UInt32) == (UInt32)(object)result) ||
836 (typeof(TResult) == typeof(Byte) && default(Byte) == (Byte)(object)result) ||
837 (typeof(TResult) == typeof(SByte) && default(SByte) == (SByte)(object)result) ||
838 (typeof(TResult) == typeof(Char) && default(Char) == (Char)(object)result) ||
839 (typeof(TResult) == typeof(Decimal) && default(Decimal) == (Decimal)(object)result) ||
840 (typeof(TResult) == typeof(Int64) && default(Int64) == (Int64)(object)result) ||
841 (typeof(TResult) == typeof(UInt64) && default(UInt64) == (UInt64)(object)result) ||
842 (typeof(TResult) == typeof(Int16) && default(Int16) == (Int16)(object)result) ||
843 (typeof(TResult) == typeof(UInt16) && default(UInt16) == (UInt16)(object)result) ||
844 (typeof(TResult) == typeof(IntPtr) && default(IntPtr) == (IntPtr)(object)result) ||
845 (typeof(TResult) == typeof(UIntPtr) && default(UIntPtr) == (UIntPtr)(object)result))
847 return s_defaultResultTask;
850 else if (result == null) // optimized away for value types
852 return s_defaultResultTask;
855 // No cached task is available. Manufacture a new one for this result.
856 return new Task<TResult>(result);
860 /// <summary>Provides a cache of closed generic tasks for async methods.</summary>
861 internal static class AsyncTaskCache
863 // All static members are initialized inline to ensure type is beforefieldinit
865 /// <summary>A cached Task{Boolean}.Result == true.</summary>
866 internal readonly static Task<Boolean> TrueTask = CreateCacheableTask(true);
867 /// <summary>A cached Task{Boolean}.Result == false.</summary>
868 internal readonly static Task<Boolean> FalseTask = CreateCacheableTask(false);
870 /// <summary>The cache of Task{Int32}.</summary>
871 internal readonly static Task<Int32>[] Int32Tasks = CreateInt32Tasks();
872 /// <summary>The minimum value, inclusive, for which we want a cached task.</summary>
873 internal const Int32 INCLUSIVE_INT32_MIN = -1;
874 /// <summary>The maximum value, exclusive, for which we want a cached task.</summary>
875 internal const Int32 EXCLUSIVE_INT32_MAX = 9;
876 /// <summary>Creates an array of cached tasks for the values in the range [INCLUSIVE_MIN,EXCLUSIVE_MAX).</summary>
877 private static Task<Int32>[] CreateInt32Tasks()
879 Debug.Assert(EXCLUSIVE_INT32_MAX >= INCLUSIVE_INT32_MIN, "Expected max to be at least min");
880 var tasks = new Task<Int32>[EXCLUSIVE_INT32_MAX - INCLUSIVE_INT32_MIN];
881 for (int i = 0; i < tasks.Length; i++)
883 tasks[i] = CreateCacheableTask(i + INCLUSIVE_INT32_MIN);
888 /// <summary>Creates a non-disposable task.</summary>
889 /// <typeparam name="TResult">Specifies the result type.</typeparam>
890 /// <param name="result">The result for the task.</param>
891 /// <returns>The cacheable task.</returns>
892 internal static Task<TResult> CreateCacheableTask<TResult>(TResult result) =>
893 new Task<TResult>(false, result, (TaskCreationOptions)InternalTaskOptions.DoNotDispose, default(CancellationToken));
897 /// An interface implemented by all <see cref="AsyncStateMachineBox{TStateMachine, TResult}"/> instances, regardless of generics.
899 internal interface IAsyncStateMachineBox : ITaskCompletionAction
902 /// Gets an action for moving forward the contained state machine.
903 /// This will lazily-allocate the delegate as needed.
905 Action MoveNextAction { get; }
907 /// <summary>Gets the state machine as a boxed object. This should only be used for debugging purposes.</summary>
908 IAsyncStateMachine GetStateMachineObject();
911 /// <summary>Shared helpers for manipulating state related to async state machines.</summary>
912 internal static class AsyncMethodBuilderCore // debugger depends on this exact name
914 /// <summary>Gets whether we should be tracking async method completions for eventing.</summary>
915 internal static bool TrackAsyncMethodCompletion
917 [MethodImpl(MethodImplOptions.AggressiveInlining)]
918 get => TplEtwProvider.Log.IsEnabled(EventLevel.Warning, TplEtwProvider.Keywords.AsyncMethod);
921 /// <summary>Gets a description of the state of the state machine object, suitable for debug purposes.</summary>
922 /// <param name="stateMachine">The state machine object.</param>
923 /// <returns>A description of the state machine.</returns>
924 internal static string GetAsyncStateMachineDescription(IAsyncStateMachine stateMachine)
926 Debug.Assert(stateMachine != null);
928 Type stateMachineType = stateMachine.GetType();
929 FieldInfo[] fields = stateMachineType.GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
931 var sb = new StringBuilder();
932 sb.AppendLine(stateMachineType.FullName);
933 foreach (FieldInfo fi in fields)
935 sb.AppendLine($" {fi.Name}: {fi.GetValue(stateMachine)}");
937 return sb.ToString();
940 internal static Action OutputAsyncCausalityEvents(Task task, Action continuation) =>
941 CreateContinuationWrapper(continuation, (innerContinuation, innerTask) =>
943 AsyncCausalityTracer.TraceSynchronousWorkStart(CausalityTraceLevel.Required, innerTask.Id, CausalitySynchronousWork.Execution);
944 innerContinuation.Invoke(); // Invoke the original continuation
945 AsyncCausalityTracer.TraceSynchronousWorkCompletion(CausalityTraceLevel.Required, CausalitySynchronousWork.Execution);
948 internal static Action CreateContinuationWrapper(Action continuation, Action<Action,Task> invokeAction, Task innerTask) =>
949 new ContinuationWrapper(continuation, invokeAction, innerTask).Invoke;
951 internal static Action TryGetStateMachineForDebugger(Action action) // debugger depends on this exact name/signature
953 object target = action.Target;
955 target is IAsyncStateMachineBox sm ? sm.GetStateMachineObject().MoveNext :
956 target is ContinuationWrapper cw ? TryGetStateMachineForDebugger(cw._continuation) :
960 internal static Task TryGetContinuationTask(Action continuation) =>
961 (continuation?.Target as ContinuationWrapper)?._innerTask;
963 /// <summary>Throws the exception on the ThreadPool.</summary>
964 /// <param name="exception">The exception to propagate.</param>
965 /// <param name="targetContext">The target context on which to propagate the exception. Null to use the ThreadPool.</param>
966 internal static void ThrowAsync(Exception exception, SynchronizationContext targetContext)
968 // Capture the exception into an ExceptionDispatchInfo so that its
969 // stack trace and Watson bucket info will be preserved
970 var edi = ExceptionDispatchInfo.Capture(exception);
972 // If the user supplied a SynchronizationContext...
973 if (targetContext != null)
977 // Post the throwing of the exception to that context, and return.
978 targetContext.Post(state => ((ExceptionDispatchInfo)state).Throw(), edi);
981 catch (Exception postException)
983 // If something goes horribly wrong in the Post, we'll
984 // propagate both exceptions on the ThreadPool
985 edi = ExceptionDispatchInfo.Capture(new AggregateException(exception, postException));
989 // If we have the new error reporting APIs, report this error. Otherwise, Propagate the exception(s) on the ThreadPool
990 #if FEATURE_COMINTEROP
991 if (!WindowsRuntimeMarshal.ReportUnhandledError(edi.SourceException))
992 #endif // FEATURE_COMINTEROP
994 ThreadPool.QueueUserWorkItem(state => ((ExceptionDispatchInfo)state).Throw(), edi);
999 /// Logically we pass just an Action (delegate) to a task for its action to 'ContinueWith' when it completes.
1000 /// However debuggers and profilers need more information about what that action is. (In particular what
1001 /// the action after that is and after that. To solve this problem we create a 'ContinuationWrapper
1002 /// which when invoked just does the original action (the invoke action), but also remembers other information
1003 /// (like the action after that (which is also a ContinuationWrapper and thus form a linked list).
1004 // We also store that task if the action is associate with at task.
1006 private sealed class ContinuationWrapper
1008 private readonly Action<Action, Task> _invokeAction; // This wrapper is an action that wraps another action, this is that Action.
1009 internal readonly Action _continuation; // This is continuation which will happen after m_invokeAction (and is probably a ContinuationWrapper)
1010 internal readonly Task _innerTask; // If the continuation is logically going to invoke a task, this is that task (may be null)
1012 internal ContinuationWrapper(Action continuation, Action<Action, Task> invokeAction, Task innerTask)
1014 Debug.Assert(continuation != null, "Expected non-null continuation");
1015 Debug.Assert(invokeAction != null, "Expected non-null continuation");
1017 _invokeAction = invokeAction;
1018 _continuation = continuation;
1019 _innerTask = innerTask ?? TryGetContinuationTask(continuation); // if we don't have a task, see if our continuation is a wrapper and use that.
1022 internal void Invoke() => _invokeAction(_continuation, _innerTask);