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;
24 namespace System.Runtime.CompilerServices
27 /// Provides a builder for asynchronous methods that return void.
28 /// This type is intended for compiler use only.
30 public struct AsyncVoidMethodBuilder
32 /// <summary>The synchronization context associated with this operation.</summary>
33 private SynchronizationContext _synchronizationContext;
34 /// <summary>The builder this void builder wraps.</summary>
35 private AsyncTaskMethodBuilder _builder; // mutable struct: must not be readonly
37 /// <summary>Initializes a new <see cref="AsyncVoidMethodBuilder"/>.</summary>
38 /// <returns>The initialized <see cref="AsyncVoidMethodBuilder"/>.</returns>
39 public static AsyncVoidMethodBuilder Create()
41 SynchronizationContext sc = SynchronizationContext.Current;
42 sc?.OperationStarted();
43 return new AsyncVoidMethodBuilder() { _synchronizationContext = sc };
46 /// <summary>Initiates the builder's execution with the associated state machine.</summary>
47 /// <typeparam name="TStateMachine">Specifies the type of the state machine.</typeparam>
48 /// <param name="stateMachine">The state machine instance, passed by reference.</param>
49 /// <exception cref="System.ArgumentNullException">The <paramref name="stateMachine"/> argument was null (Nothing in Visual Basic).</exception>
51 public void Start<TStateMachine>(ref TStateMachine stateMachine) where TStateMachine : IAsyncStateMachine =>
52 _builder.Start(ref stateMachine);
54 /// <summary>Associates the builder with the state machine it represents.</summary>
55 /// <param name="stateMachine">The heap-allocated state machine object.</param>
56 /// <exception cref="System.ArgumentNullException">The <paramref name="stateMachine"/> argument was null (Nothing in Visual Basic).</exception>
57 /// <exception cref="System.InvalidOperationException">The builder is incorrectly initialized.</exception>
58 public void SetStateMachine(IAsyncStateMachine stateMachine) =>
59 _builder.SetStateMachine(stateMachine);
62 /// Schedules the specified state machine to be pushed forward when the specified awaiter completes.
64 /// <typeparam name="TAwaiter">Specifies the type of the awaiter.</typeparam>
65 /// <typeparam name="TStateMachine">Specifies the type of the state machine.</typeparam>
66 /// <param name="awaiter">The awaiter.</param>
67 /// <param name="stateMachine">The state machine.</param>
68 public void AwaitOnCompleted<TAwaiter, TStateMachine>(
69 ref TAwaiter awaiter, ref TStateMachine stateMachine)
70 where TAwaiter : INotifyCompletion
71 where TStateMachine : IAsyncStateMachine =>
72 _builder.AwaitOnCompleted(ref awaiter, ref stateMachine);
75 /// Schedules the specified state machine to be pushed forward when the specified awaiter completes.
77 /// <typeparam name="TAwaiter">Specifies the type of the awaiter.</typeparam>
78 /// <typeparam name="TStateMachine">Specifies the type of the state machine.</typeparam>
79 /// <param name="awaiter">The awaiter.</param>
80 /// <param name="stateMachine">The state machine.</param>
81 public void AwaitUnsafeOnCompleted<TAwaiter, TStateMachine>(
82 ref TAwaiter awaiter, ref TStateMachine stateMachine)
83 where TAwaiter : ICriticalNotifyCompletion
84 where TStateMachine : IAsyncStateMachine =>
85 _builder.AwaitUnsafeOnCompleted(ref awaiter, ref stateMachine);
87 /// <summary>Completes the method builder successfully.</summary>
88 public void SetResult()
90 if (AsyncCausalityTracer.LoggingOn)
92 AsyncCausalityTracer.TraceOperationCompletion(CausalityTraceLevel.Required, this.Task.Id, AsyncCausalityStatus.Completed);
95 // Mark the builder as completed. As this is a void-returning method, this mostly
96 // doesn't matter, but it can affect things like debug events related to finalization.
99 if (_synchronizationContext != null)
101 NotifySynchronizationContextOfCompletion();
105 /// <summary>Faults the method builder with an exception.</summary>
106 /// <param name="exception">The exception that is the cause of this fault.</param>
107 /// <exception cref="System.ArgumentNullException">The <paramref name="exception"/> argument is null (Nothing in Visual Basic).</exception>
108 /// <exception cref="System.InvalidOperationException">The builder is not initialized.</exception>
109 public void SetException(Exception exception)
111 if (exception == null)
113 ThrowHelper.ThrowArgumentNullException(ExceptionArgument.exception);
116 if (AsyncCausalityTracer.LoggingOn)
118 AsyncCausalityTracer.TraceOperationCompletion(CausalityTraceLevel.Required, this.Task.Id, AsyncCausalityStatus.Error);
121 if (_synchronizationContext != null)
123 // If we captured a synchronization context, Post the throwing of the exception to it
124 // and decrement its outstanding operation count.
127 AsyncMethodBuilderCore.ThrowAsync(exception, targetContext: _synchronizationContext);
131 NotifySynchronizationContextOfCompletion();
136 // Otherwise, queue the exception to be thrown on the ThreadPool. This will
137 // result in a crash unless legacy exception behavior is enabled by a config
138 // file or a CLR host.
139 AsyncMethodBuilderCore.ThrowAsync(exception, targetContext: null);
142 // The exception was propagated already; we don't need or want to fault the builder, just mark it as completed.
143 _builder.SetResult();
146 /// <summary>Notifies the current synchronization context that the operation completed.</summary>
147 private void NotifySynchronizationContextOfCompletion()
149 Debug.Assert(_synchronizationContext != null, "Must only be used with a non-null context.");
152 _synchronizationContext.OperationCompleted();
154 catch (Exception exc)
156 // If the interaction with the SynchronizationContext goes awry,
157 // fall back to propagating on the ThreadPool.
158 AsyncMethodBuilderCore.ThrowAsync(exc, targetContext: null);
162 /// <summary>Lazily instantiate the Task in a non-thread-safe manner.</summary>
163 private Task Task => _builder.Task;
166 /// Gets an object that may be used to uniquely identify this builder to the debugger.
169 /// This property lazily instantiates the ID in a non-thread-safe manner.
170 /// It must only be used by the debugger and AsyncCausalityTracer in a single-threaded manner.
172 internal object ObjectIdForDebugger => _builder.ObjectIdForDebugger;
176 /// Provides a builder for asynchronous methods that return <see cref="System.Threading.Tasks.Task"/>.
177 /// This type is intended for compiler use only.
180 /// AsyncTaskMethodBuilder is a value type, and thus it is copied by value.
181 /// Prior to being copied, one of its Task, SetResult, or SetException members must be accessed,
182 /// or else the copies may end up building distinct Task instances.
184 public struct AsyncTaskMethodBuilder
186 /// <summary>A cached VoidTaskResult task used for builders that complete synchronously.</summary>
187 private readonly static Task<VoidTaskResult> s_cachedCompleted = AsyncTaskMethodBuilder<VoidTaskResult>.s_defaultResultTask;
189 /// <summary>The generic builder object to which this non-generic instance delegates.</summary>
190 private AsyncTaskMethodBuilder<VoidTaskResult> m_builder; // mutable struct: must not be readonly. Debugger depends on the exact name of this field.
192 /// <summary>Initializes a new <see cref="AsyncTaskMethodBuilder"/>.</summary>
193 /// <returns>The initialized <see cref="AsyncTaskMethodBuilder"/>.</returns>
194 public static AsyncTaskMethodBuilder Create() => default(AsyncTaskMethodBuilder);
196 /// <summary>Initiates the builder's execution with the associated state machine.</summary>
197 /// <typeparam name="TStateMachine">Specifies the type of the state machine.</typeparam>
198 /// <param name="stateMachine">The state machine instance, passed by reference.</param>
199 [DebuggerStepThrough]
200 public void Start<TStateMachine>(ref TStateMachine stateMachine) where TStateMachine : IAsyncStateMachine =>
201 m_builder.Start(ref stateMachine);
203 /// <summary>Associates the builder with the state machine it represents.</summary>
204 /// <param name="stateMachine">The heap-allocated state machine object.</param>
205 /// <exception cref="System.ArgumentNullException">The <paramref name="stateMachine"/> argument was null (Nothing in Visual Basic).</exception>
206 /// <exception cref="System.InvalidOperationException">The builder is incorrectly initialized.</exception>
207 public void SetStateMachine(IAsyncStateMachine stateMachine) =>
208 m_builder.SetStateMachine(stateMachine);
211 /// Schedules the specified state machine to be pushed forward when the specified awaiter completes.
213 /// <typeparam name="TAwaiter">Specifies the type of the awaiter.</typeparam>
214 /// <typeparam name="TStateMachine">Specifies the type of the state machine.</typeparam>
215 /// <param name="awaiter">The awaiter.</param>
216 /// <param name="stateMachine">The state machine.</param>
217 public void AwaitOnCompleted<TAwaiter, TStateMachine>(
218 ref TAwaiter awaiter, ref TStateMachine stateMachine)
219 where TAwaiter : INotifyCompletion
220 where TStateMachine : IAsyncStateMachine =>
221 m_builder.AwaitOnCompleted(ref awaiter, ref stateMachine);
224 /// Schedules the specified state machine to be pushed forward when the specified awaiter completes.
226 /// <typeparam name="TAwaiter">Specifies the type of the awaiter.</typeparam>
227 /// <typeparam name="TStateMachine">Specifies the type of the state machine.</typeparam>
228 /// <param name="awaiter">The awaiter.</param>
229 /// <param name="stateMachine">The state machine.</param>
230 public void AwaitUnsafeOnCompleted<TAwaiter, TStateMachine>(
231 ref TAwaiter awaiter, ref TStateMachine stateMachine)
232 where TAwaiter : ICriticalNotifyCompletion
233 where TStateMachine : IAsyncStateMachine =>
234 m_builder.AwaitUnsafeOnCompleted(ref awaiter, ref stateMachine);
236 /// <summary>Gets the <see cref="System.Threading.Tasks.Task"/> for this builder.</summary>
237 /// <returns>The <see cref="System.Threading.Tasks.Task"/> representing the builder's asynchronous operation.</returns>
238 /// <exception cref="System.InvalidOperationException">The builder is not initialized.</exception>
241 [MethodImpl(MethodImplOptions.AggressiveInlining)]
242 get => m_builder.Task;
246 /// Completes the <see cref="System.Threading.Tasks.Task"/> in the
247 /// <see cref="System.Threading.Tasks.TaskStatus">RanToCompletion</see> state.
249 /// <exception cref="System.InvalidOperationException">The builder is not initialized.</exception>
250 /// <exception cref="System.InvalidOperationException">The task has already completed.</exception>
251 public void SetResult() => m_builder.SetResult(s_cachedCompleted); // Using s_cachedCompleted is faster than using s_defaultResultTask.
254 /// Completes the <see cref="System.Threading.Tasks.Task"/> in the
255 /// <see cref="System.Threading.Tasks.TaskStatus">Faulted</see> state with the specified exception.
257 /// <param name="exception">The <see cref="System.Exception"/> to use to fault the task.</param>
258 /// <exception cref="System.ArgumentNullException">The <paramref name="exception"/> argument is null (Nothing in Visual Basic).</exception>
259 /// <exception cref="System.InvalidOperationException">The builder is not initialized.</exception>
260 /// <exception cref="System.InvalidOperationException">The task has already completed.</exception>
261 public void SetException(Exception exception) => m_builder.SetException(exception);
264 /// Called by the debugger to request notification when the first wait operation
265 /// (await, Wait, Result, etc.) on this builder's task completes.
267 /// <param name="enabled">
268 /// true to enable notification; false to disable a previously set notification.
270 internal void SetNotificationForWaitCompletion(bool enabled) => m_builder.SetNotificationForWaitCompletion(enabled);
273 /// Gets an object that may be used to uniquely identify this builder to the debugger.
276 /// This property lazily instantiates the ID in a non-thread-safe manner.
277 /// It must only be used by the debugger and tracing purposes, and only in a single-threaded manner
278 /// when no other threads are in the middle of accessing this property or this.Task.
280 internal object ObjectIdForDebugger => m_builder.ObjectIdForDebugger;
284 /// Provides a builder for asynchronous methods that return <see cref="System.Threading.Tasks.Task{TResult}"/>.
285 /// This type is intended for compiler use only.
288 /// AsyncTaskMethodBuilder{TResult} is a value type, and thus it is copied by value.
289 /// Prior to being copied, one of its Task, SetResult, or SetException members must be accessed,
290 /// or else the copies may end up building distinct Task instances.
292 public struct AsyncTaskMethodBuilder<TResult>
294 /// <summary>A cached task for default(TResult).</summary>
295 internal readonly static Task<TResult> s_defaultResultTask = AsyncTaskCache.CreateCacheableTask(default(TResult));
297 /// <summary>The lazily-initialized built task.</summary>
298 private Task<TResult> m_task; // lazily-initialized: must not be readonly. Debugger depends on the exact name of this field.
300 /// <summary>Initializes a new <see cref="AsyncTaskMethodBuilder"/>.</summary>
301 /// <returns>The initialized <see cref="AsyncTaskMethodBuilder"/>.</returns>
302 public static AsyncTaskMethodBuilder<TResult> Create()
304 return default(AsyncTaskMethodBuilder<TResult>);
305 // NOTE: If this method is ever updated to perform more initialization,
306 // other Create methods like AsyncTaskMethodBuilder.Create and
307 // AsyncValueTaskMethodBuilder.Create must be updated to call this.
310 /// <summary>Initiates the builder's execution with the associated state machine.</summary>
311 /// <typeparam name="TStateMachine">Specifies the type of the state machine.</typeparam>
312 /// <param name="stateMachine">The state machine instance, passed by reference.</param>
313 [DebuggerStepThrough]
314 public void Start<TStateMachine>(ref TStateMachine stateMachine) where TStateMachine : IAsyncStateMachine
316 if (stateMachine == null) // TStateMachines are generally non-nullable value types, so this check will be elided
318 ThrowHelper.ThrowArgumentNullException(ExceptionArgument.stateMachine);
321 // Run the MoveNext method within a copy-on-write ExecutionContext scope.
322 // This allows us to undo any ExecutionContext changes made in MoveNext,
323 // so that they won't "leak" out of the first await.
325 Thread currentThread = Thread.CurrentThread;
326 ExecutionContextSwitcher ecs = default(ExecutionContextSwitcher);
329 ExecutionContext.EstablishCopyOnWriteScope(currentThread, ref ecs);
330 stateMachine.MoveNext();
334 ecs.Undo(currentThread);
338 /// <summary>Associates the builder with the state machine it represents.</summary>
339 /// <param name="stateMachine">The heap-allocated state machine object.</param>
340 /// <exception cref="System.ArgumentNullException">The <paramref name="stateMachine"/> argument was null (Nothing in Visual Basic).</exception>
341 /// <exception cref="System.InvalidOperationException">The builder is incorrectly initialized.</exception>
342 public void SetStateMachine(IAsyncStateMachine stateMachine)
344 if (stateMachine == null)
346 ThrowHelper.ThrowArgumentNullException(ExceptionArgument.stateMachine);
351 ThrowHelper.ThrowInvalidOperationException(ExceptionResource.AsyncMethodBuilder_InstanceNotInitialized);
354 // SetStateMachine was originally needed in order to store the boxed state machine reference into
355 // the boxed copy. Now that a normal box is no longer used, SetStateMachine is also legacy. We need not
356 // do anything here, and thus assert to ensure we're not calling this from our own implementations.
357 Debug.Fail("SetStateMachine should not be used.");
361 /// Schedules the specified state machine to be pushed forward when the specified awaiter completes.
363 /// <typeparam name="TAwaiter">Specifies the type of the awaiter.</typeparam>
364 /// <typeparam name="TStateMachine">Specifies the type of the state machine.</typeparam>
365 /// <param name="awaiter">The awaiter.</param>
366 /// <param name="stateMachine">The state machine.</param>
367 public void AwaitOnCompleted<TAwaiter, TStateMachine>(
368 ref TAwaiter awaiter, ref TStateMachine stateMachine)
369 where TAwaiter : INotifyCompletion
370 where TStateMachine : IAsyncStateMachine
374 awaiter.OnCompleted(GetStateMachineBox(ref stateMachine).MoveNextAction);
378 AsyncMethodBuilderCore.ThrowAsync(e, targetContext: null);
383 /// Schedules the specified state machine to be pushed forward when the specified awaiter completes.
385 /// <typeparam name="TAwaiter">Specifies the type of the awaiter.</typeparam>
386 /// <typeparam name="TStateMachine">Specifies the type of the state machine.</typeparam>
387 /// <param name="awaiter">The awaiter.</param>
388 /// <param name="stateMachine">The state machine.</param>
389 public void AwaitUnsafeOnCompleted<TAwaiter, TStateMachine>(
390 ref TAwaiter awaiter, ref TStateMachine stateMachine)
391 where TAwaiter : ICriticalNotifyCompletion
392 where TStateMachine : IAsyncStateMachine
394 IAsyncStateMachineBox box = GetStateMachineBox(ref stateMachine);
396 // The null tests here ensure that the jit can optimize away the interface
397 // tests when TAwaiter is is a ref type.
398 if ((null != (object)default(TAwaiter)) && (awaiter is ITaskAwaiter))
400 ref TaskAwaiter ta = ref Unsafe.As<TAwaiter, TaskAwaiter>(ref awaiter); // relies on TaskAwaiter/TaskAwaiter<T> having the same layout
401 TaskAwaiter.UnsafeOnCompletedInternal(ta.m_task, box, continueOnCapturedContext: true);
403 else if ((null != (object)default(TAwaiter)) && (awaiter is IConfiguredTaskAwaiter))
405 ref ConfiguredTaskAwaitable.ConfiguredTaskAwaiter ta = ref Unsafe.As<TAwaiter, ConfiguredTaskAwaitable.ConfiguredTaskAwaiter>(ref awaiter);
406 TaskAwaiter.UnsafeOnCompletedInternal(ta.m_task, box, ta.m_continueOnCapturedContext);
408 else if ((null != (object)default(TAwaiter)) && (awaiter is IValueTaskAwaiter))
410 Task t = ((IValueTaskAwaiter)awaiter).GetTask();
411 TaskAwaiter.UnsafeOnCompletedInternal(t, box, continueOnCapturedContext: true);
413 else if ((null != (object)default(TAwaiter)) && (awaiter is IConfiguredValueTaskAwaiter))
415 (Task task, bool continueOnCapturedContext) t = ((IConfiguredValueTaskAwaiter)awaiter).GetTask();
416 TaskAwaiter.UnsafeOnCompletedInternal(t.task, box, t.continueOnCapturedContext);
418 // The awaiter isn't specially known. Fall back to doing a normal await.
423 awaiter.UnsafeOnCompleted(box.MoveNextAction);
427 AsyncMethodBuilderCore.ThrowAsync(e, targetContext: null);
432 /// <summary>Gets the "boxed" state machine object.</summary>
433 /// <typeparam name="TStateMachine">Specifies the type of the async state machine.</typeparam>
434 /// <param name="stateMachine">The state machine.</param>
435 /// <returns>The "boxed" state machine.</returns>
436 private IAsyncStateMachineBox GetStateMachineBox<TStateMachine>(
437 ref TStateMachine stateMachine)
438 where TStateMachine : IAsyncStateMachine
440 ExecutionContext currentContext = ExecutionContext.Capture();
442 // Check first for the most common case: not the first yield in an async method.
443 // In this case, the first yield will have already "boxed" the state machine in
444 // a strongly-typed manner into an AsyncStateMachineBox. It will already contain
445 // the state machine as well as a MoveNextDelegate and a context. The only thing
446 // we might need to do is update the context if that's changed since it was stored.
447 if (m_task is AsyncStateMachineBox<TStateMachine> stronglyTypedBox)
449 if (stronglyTypedBox.Context != currentContext)
451 stronglyTypedBox.Context = currentContext;
453 return stronglyTypedBox;
456 // The least common case: we have a weakly-typed boxed. This results if the debugger
457 // or some other use of reflection accesses a property like ObjectIdForDebugger or a
458 // method like SetNotificationForWaitCompletion prior to the first await happening. In
459 // such situations, we need to get an object to represent the builder, but we don't yet
460 // know the type of the state machine, and thus can't use TStateMachine. Instead, we
461 // use the IAsyncStateMachine interface, which all TStateMachines implement. This will
462 // result in a boxing allocation when storing the TStateMachine if it's a struct, but
463 // this only happens in active debugging scenarios where such performance impact doesn't
465 if (m_task is AsyncStateMachineBox<IAsyncStateMachine> weaklyTypedBox)
467 // If this is the first await, we won't yet have a state machine, so store it.
468 if (weaklyTypedBox.StateMachine == null)
470 Debugger.NotifyOfCrossThreadDependency(); // same explanation as with usage below
471 weaklyTypedBox.StateMachine = stateMachine;
474 // Update the context. This only happens with a debugger, so no need to spend
475 // extra IL checking for equality before doing the assignment.
476 weaklyTypedBox.Context = currentContext;
477 return weaklyTypedBox;
480 // Alert a listening debugger that we can't make forward progress unless it slips threads.
481 // If we don't do this, and a method that uses "await foo;" is invoked through funceval,
482 // we could end up hooking up a callback to push forward the async method's state machine,
483 // the debugger would then abort the funceval after it takes too long, and then continuing
484 // execution could result in another callback being hooked up. At that point we have
485 // multiple callbacks registered to push the state machine, which could result in bad behavior.
486 Debugger.NotifyOfCrossThreadDependency();
488 // At this point, m_task should really be null, in which case we want to create the box.
489 // However, in a variety of debugger-related (erroneous) situations, it might be non-null,
490 // e.g. if the Task property is examined in a Watch window, forcing it to be lazily-intialized
491 // as a Task<TResult> rather than as an AsyncStateMachineBox. The worst that happens in such
492 // cases is we lose the ability to properly step in the debugger, as the debugger uses that
493 // object's identity to track this specific builder/state machine. As such, we proceed to
494 // overwrite whatever's there anyway, even if it's non-null.
495 var box = AsyncMethodBuilderCore.TrackAsyncMethodCompletion ?
496 new DebugFinalizableAsyncStateMachineBox<TStateMachine>() :
497 new AsyncStateMachineBox<TStateMachine>();
498 m_task = box; // important: this must be done before storing stateMachine into box.StateMachine!
499 box.StateMachine = stateMachine;
500 box.Context = currentContext;
505 /// Provides an async state machine box with a finalizer that will fire an EventSource
506 /// event about the state machine if it's being finalized without having been completed.
508 /// <typeparam name="TStateMachine">Specifies the type of the state machine.</typeparam>
509 private sealed class DebugFinalizableAsyncStateMachineBox<TStateMachine> :
510 AsyncStateMachineBox<TStateMachine>
511 where TStateMachine : IAsyncStateMachine
513 ~DebugFinalizableAsyncStateMachineBox()
515 // If the state machine is being finalized, something went wrong during its processing,
516 // e.g. it awaited something that got collected without itself having been completed.
517 // Fire an event with details about the state machine to help with debugging.
518 if (!IsCompleted) // double-check it's not completed, just to help minimize false positives
520 TplEtwProvider.Log.IncompleteAsyncMethod(this);
525 /// <summary>A strongly-typed box for Task-based async state machines.</summary>
526 /// <typeparam name="TStateMachine">Specifies the type of the state machine.</typeparam>
527 /// <typeparam name="TResult">Specifies the type of the Task's result.</typeparam>
528 private class AsyncStateMachineBox<TStateMachine> :
529 Task<TResult>, IAsyncStateMachineBox
530 where TStateMachine : IAsyncStateMachine
532 /// <summary>Delegate used to invoke on an ExecutionContext when passed an instance of this box type.</summary>
533 private static readonly ContextCallback s_callback = s => ((AsyncStateMachineBox<TStateMachine>)s).StateMachine.MoveNext();
535 /// <summary>A delegate to the <see cref="MoveNext"/> method.</summary>
536 private Action _moveNextAction;
537 /// <summary>The state machine itself.</summary>
538 public TStateMachine StateMachine; // mutable struct; do not make this readonly
539 /// <summary>Captured ExecutionContext with which to invoke <see cref="MoveNextAction"/>; may be null.</summary>
540 public ExecutionContext Context;
542 /// <summary>A delegate to the <see cref="MoveNext"/> method.</summary>
543 public Action MoveNextAction =>
545 (_moveNextAction = AsyncCausalityTracer.LoggingOn ? AsyncMethodBuilderCore.OutputAsyncCausalityEvents(this, new Action(MoveNext)) : new Action(MoveNext));
547 /// <summary>Calls MoveNext on <see cref="StateMachine"/></summary>
548 public void MoveNext()
552 StateMachine.MoveNext();
556 ExecutionContext.Run(Context, s_callback, this);
559 // In case this is a state machine box with a finalizer, suppress its finalization
560 // if it's now complete. We only need the finalizer to run if the box is collected
561 // without having been completed.
562 if (IsCompleted && AsyncMethodBuilderCore.TrackAsyncMethodCompletion)
564 GC.SuppressFinalize(this);
569 /// Calls MoveNext on <see cref="StateMachine"/>. Implements ITaskCompletionAction.Invoke so
570 /// that the state machine object may be queued directly as a continuation into a Task's
571 /// continuation slot/list.
573 /// <param name="completedTask">The completing task that caused this method to be invoked, if there was one.</param>
574 void ITaskCompletionAction.Invoke(Task completedTask) => MoveNext();
576 /// <summary>Signals to Task's continuation logic that <see cref="Invoke"/> runs arbitrary user code via MoveNext.</summary>
577 bool ITaskCompletionAction.InvokeMayRunArbitraryCode => true;
579 /// <summary>Gets the state machine as a boxed object. This should only be used for debugging purposes.</summary>
580 IAsyncStateMachine IAsyncStateMachineBox.GetStateMachineObject() => StateMachine; // likely boxes, only use for debugging
583 /// <summary>Gets the <see cref="System.Threading.Tasks.Task{TResult}"/> for this builder.</summary>
584 /// <returns>The <see cref="System.Threading.Tasks.Task{TResult}"/> representing the builder's asynchronous operation.</returns>
585 public Task<TResult> Task
587 [MethodImpl(MethodImplOptions.AggressiveInlining)]
588 get => m_task ?? InitializeTaskAsPromise();
592 /// Initializes the task, which must not yet be initialized. Used only when the Task is being forced into
593 /// existence when no state machine is needed, e.g. when the builder is being synchronously completed with
594 /// an exception, when the builder is being used out of the context of an async method, etc.
596 [MethodImpl(MethodImplOptions.NoInlining)]
597 private Task<TResult> InitializeTaskAsPromise()
599 Debug.Assert(m_task == null);
600 return (m_task = new Task<TResult>());
604 /// Initializes the task, which must not yet be initialized. Used only when the Task is being forced into
605 /// existence due to the debugger trying to enable step-out/step-over/etc. prior to the first await yielding
606 /// in an async method. In that case, we don't know the actual TStateMachine type, so we're forced to
607 /// use IAsyncStateMachine instead.
609 [MethodImpl(MethodImplOptions.NoInlining)]
610 private Task<TResult> InitializeTaskAsStateMachineBox()
612 Debug.Assert(m_task == null);
613 return (m_task = AsyncMethodBuilderCore.TrackAsyncMethodCompletion ?
614 new DebugFinalizableAsyncStateMachineBox<IAsyncStateMachine>() :
615 new AsyncStateMachineBox<IAsyncStateMachine>());
619 /// Completes the <see cref="System.Threading.Tasks.Task{TResult}"/> in the
620 /// <see cref="System.Threading.Tasks.TaskStatus">RanToCompletion</see> state with the specified result.
622 /// <param name="result">The result to use to complete the task.</param>
623 /// <exception cref="System.InvalidOperationException">The task has already completed.</exception>
624 public void SetResult(TResult result)
626 // Get the currently stored task, which will be non-null if get_Task has already been accessed.
627 // If there isn't one, get a task and store it.
630 m_task = GetTaskForResult(result);
631 Debug.Assert(m_task != null, $"{nameof(GetTaskForResult)} should never return null");
635 // Slow path: complete the existing task.
636 SetExistingTaskResult(result);
640 /// <summary>Completes the already initialized task with the specified result.</summary>
641 /// <param name="result">The result to use to complete the task.</param>
642 private void SetExistingTaskResult(TResult result)
644 Debug.Assert(m_task != null, "Expected non-null task");
646 if (AsyncCausalityTracer.LoggingOn || System.Threading.Tasks.Task.s_asyncDebuggingEnabled)
648 LogExistingTaskCompletion();
651 if (!m_task.TrySetResult(result))
653 ThrowHelper.ThrowInvalidOperationException(ExceptionResource.TaskT_TransitionToFinal_AlreadyCompleted);
657 /// <summary>Handles logging for the successful completion of an operation.</summary>
658 private void LogExistingTaskCompletion()
660 Debug.Assert(m_task != null);
662 if (AsyncCausalityTracer.LoggingOn)
664 AsyncCausalityTracer.TraceOperationCompletion(CausalityTraceLevel.Required, m_task.Id, AsyncCausalityStatus.Completed);
667 // only log if we have a real task that was previously created
668 if (System.Threading.Tasks.Task.s_asyncDebuggingEnabled)
670 System.Threading.Tasks.Task.RemoveFromActiveTasks(m_task.Id);
675 /// Completes the builder by using either the supplied completed task, or by completing
676 /// the builder's previously accessed task using default(TResult).
678 /// <param name="completedTask">A task already completed with the value default(TResult).</param>
679 /// <exception cref="System.InvalidOperationException">The task has already completed.</exception>
680 internal void SetResult(Task<TResult> completedTask)
682 Debug.Assert(completedTask != null, "Expected non-null task");
683 Debug.Assert(completedTask.IsCompletedSuccessfully, "Expected a successfully completed task");
685 // Get the currently stored task, which will be non-null if get_Task has already been accessed.
686 // If there isn't one, store the supplied completed task.
689 m_task = completedTask;
693 // Otherwise, complete the task that's there.
694 SetExistingTaskResult(default(TResult));
699 /// Completes the <see cref="System.Threading.Tasks.Task{TResult}"/> in the
700 /// <see cref="System.Threading.Tasks.TaskStatus">Faulted</see> state with the specified exception.
702 /// <param name="exception">The <see cref="System.Exception"/> to use to fault the task.</param>
703 /// <exception cref="System.ArgumentNullException">The <paramref name="exception"/> argument is null (Nothing in Visual Basic).</exception>
704 /// <exception cref="System.InvalidOperationException">The task has already completed.</exception>
705 public void SetException(Exception exception)
707 if (exception == null)
709 ThrowHelper.ThrowArgumentNullException(ExceptionArgument.exception);
712 // Get the task, forcing initialization if it hasn't already been initialized.
713 Task<TResult> task = this.Task;
715 // If the exception represents cancellation, cancel the task. Otherwise, fault the task.
716 var oce = exception as OperationCanceledException;
717 bool successfullySet = oce != null ?
718 task.TrySetCanceled(oce.CancellationToken, oce) :
719 task.TrySetException(exception);
721 // Unlike with TaskCompletionSource, we do not need to spin here until _taskAndStateMachine is completed,
722 // since AsyncTaskMethodBuilder.SetException should not be immediately followed by any code
723 // that depends on the task having completely completed. Moreover, with correct usage,
724 // SetResult or SetException should only be called once, so the Try* methods should always
725 // return true, so no spinning would be necessary anyway (the spinning in TCS is only relevant
726 // if another thread completes the task first).
727 if (!successfullySet)
729 ThrowHelper.ThrowInvalidOperationException(ExceptionResource.TaskT_TransitionToFinal_AlreadyCompleted);
734 /// Called by the debugger to request notification when the first wait operation
735 /// (await, Wait, Result, etc.) on this builder's task completes.
737 /// <param name="enabled">
738 /// true to enable notification; false to disable a previously set notification.
741 /// This should only be invoked from within an asynchronous method,
742 /// and only by the debugger.
744 internal void SetNotificationForWaitCompletion(bool enabled)
746 // Get the task (forcing initialization if not already initialized), and set debug notification
747 (m_task ?? InitializeTaskAsStateMachineBox()).SetNotificationForWaitCompletion(enabled);
749 // NOTE: It's important that the debugger use builder.SetNotificationForWaitCompletion
750 // rather than builder.Task.SetNotificationForWaitCompletion. Even though the latter will
751 // lazily-initialize the task as well, it'll initialize it to a Task<T> (which is important
752 // to minimize size for cases where an ATMB is used directly by user code to avoid the
753 // allocation overhead of a TaskCompletionSource). If that's done prior to the first await,
754 // the GetMoveNextDelegate code, which needs an AsyncStateMachineBox, will end up creating
755 // a new box and overwriting the previously created task. That'll change the object identity
756 // of the task being used for wait completion notification, and no notification will
757 // ever arrive, breaking step-out behavior when stepping out before the first yielding await.
761 /// Gets an object that may be used to uniquely identify this builder to the debugger.
764 /// This property lazily instantiates the ID in a non-thread-safe manner.
765 /// It must only be used by the debugger and tracing purposes, and only in a single-threaded manner
766 /// when no other threads are in the middle of accessing this or other members that lazily initialize the task.
768 internal object ObjectIdForDebugger => m_task ?? InitializeTaskAsStateMachineBox();
771 /// Gets a task for the specified result. This will either
772 /// be a cached or new task, never null.
774 /// <param name="result">The result for which we need a task.</param>
775 /// <returns>The completed task containing the result.</returns>
776 [MethodImpl(MethodImplOptions.AggressiveInlining)] // method looks long, but for a given TResult it results in a relatively small amount of asm
777 internal static Task<TResult> GetTaskForResult(TResult result)
779 // The goal of this function is to be give back a cached task if possible,
780 // or to otherwise give back a new task. To give back a cached task,
781 // we need to be able to evaluate the incoming result value, and we need
782 // to avoid as much overhead as possible when doing so, as this function
783 // is invoked as part of the return path from every async method.
784 // Most tasks won't be cached, and thus we need the checks for those that are
785 // to be as close to free as possible. This requires some trickiness given the
786 // lack of generic specialization in .NET.
788 // Be very careful when modifying this code. It has been tuned
789 // to comply with patterns recognized by both 32-bit and 64-bit JITs.
790 // If changes are made here, be sure to look at the generated assembly, as
791 // small tweaks can have big consequences for what does and doesn't get optimized away.
793 // Note that this code only ever accesses a static field when it knows it'll
794 // find a cached value, since static fields (even if readonly and integral types)
795 // require special access helpers in this NGEN'd and domain-neutral.
797 if (null != (object)default(TResult)) // help the JIT avoid the value type branches for ref types
799 // Special case simple value types:
808 // As of .NET 4.5, the (Type)(object)result pattern used below
809 // is recognized and optimized by both 32-bit and 64-bit JITs.
811 // For Boolean, we cache all possible values.
812 if (typeof(TResult) == typeof(Boolean)) // only the relevant branches are kept for each value-type generic instantiation
814 Boolean value = (Boolean)(object)result;
815 Task<Boolean> task = value ? AsyncTaskCache.TrueTask : AsyncTaskCache.FalseTask;
816 return Unsafe.As<Task<TResult>>(task); // UnsafeCast avoids type check we know will succeed
818 // For Int32, we cache a range of common values, e.g. [-1,4).
819 else if (typeof(TResult) == typeof(Int32))
821 // Compare to constants to avoid static field access if outside of cached range.
822 // We compare to the upper bound first, as we're more likely to cache miss on the upper side than on the
823 // lower side, due to positive values being more common than negative as return values.
824 Int32 value = (Int32)(object)result;
825 if (value < AsyncTaskCache.EXCLUSIVE_INT32_MAX &&
826 value >= AsyncTaskCache.INCLUSIVE_INT32_MIN)
828 Task<Int32> task = AsyncTaskCache.Int32Tasks[value - AsyncTaskCache.INCLUSIVE_INT32_MIN];
829 return Unsafe.As<Task<TResult>>(task); // UnsafeCast avoids a type check we know will succeed
832 // For other known value types, we only special-case 0 / default(TResult).
834 (typeof(TResult) == typeof(UInt32) && default(UInt32) == (UInt32)(object)result) ||
835 (typeof(TResult) == typeof(Byte) && default(Byte) == (Byte)(object)result) ||
836 (typeof(TResult) == typeof(SByte) && default(SByte) == (SByte)(object)result) ||
837 (typeof(TResult) == typeof(Char) && default(Char) == (Char)(object)result) ||
838 (typeof(TResult) == typeof(Decimal) && default(Decimal) == (Decimal)(object)result) ||
839 (typeof(TResult) == typeof(Int64) && default(Int64) == (Int64)(object)result) ||
840 (typeof(TResult) == typeof(UInt64) && default(UInt64) == (UInt64)(object)result) ||
841 (typeof(TResult) == typeof(Int16) && default(Int16) == (Int16)(object)result) ||
842 (typeof(TResult) == typeof(UInt16) && default(UInt16) == (UInt16)(object)result) ||
843 (typeof(TResult) == typeof(IntPtr) && default(IntPtr) == (IntPtr)(object)result) ||
844 (typeof(TResult) == typeof(UIntPtr) && default(UIntPtr) == (UIntPtr)(object)result))
846 return s_defaultResultTask;
849 else if (result == null) // optimized away for value types
851 return s_defaultResultTask;
854 // No cached task is available. Manufacture a new one for this result.
855 return new Task<TResult>(result);
859 /// <summary>Provides a cache of closed generic tasks for async methods.</summary>
860 internal static class AsyncTaskCache
862 // All static members are initialized inline to ensure type is beforefieldinit
864 /// <summary>A cached Task{Boolean}.Result == true.</summary>
865 internal readonly static Task<Boolean> TrueTask = CreateCacheableTask(true);
866 /// <summary>A cached Task{Boolean}.Result == false.</summary>
867 internal readonly static Task<Boolean> FalseTask = CreateCacheableTask(false);
869 /// <summary>The cache of Task{Int32}.</summary>
870 internal readonly static Task<Int32>[] Int32Tasks = CreateInt32Tasks();
871 /// <summary>The minimum value, inclusive, for which we want a cached task.</summary>
872 internal const Int32 INCLUSIVE_INT32_MIN = -1;
873 /// <summary>The maximum value, exclusive, for which we want a cached task.</summary>
874 internal const Int32 EXCLUSIVE_INT32_MAX = 9;
875 /// <summary>Creates an array of cached tasks for the values in the range [INCLUSIVE_MIN,EXCLUSIVE_MAX).</summary>
876 private static Task<Int32>[] CreateInt32Tasks()
878 Debug.Assert(EXCLUSIVE_INT32_MAX >= INCLUSIVE_INT32_MIN, "Expected max to be at least min");
879 var tasks = new Task<Int32>[EXCLUSIVE_INT32_MAX - INCLUSIVE_INT32_MIN];
880 for (int i = 0; i < tasks.Length; i++)
882 tasks[i] = CreateCacheableTask(i + INCLUSIVE_INT32_MIN);
887 /// <summary>Creates a non-disposable task.</summary>
888 /// <typeparam name="TResult">Specifies the result type.</typeparam>
889 /// <param name="result">The result for the task.</param>
890 /// <returns>The cacheable task.</returns>
891 internal static Task<TResult> CreateCacheableTask<TResult>(TResult result) =>
892 new Task<TResult>(false, result, (TaskCreationOptions)InternalTaskOptions.DoNotDispose, default(CancellationToken));
896 /// An interface implemented by all <see cref="AsyncStateMachineBox{TStateMachine, TResult}"/> instances, regardless of generics.
898 internal interface IAsyncStateMachineBox : ITaskCompletionAction
901 /// Gets an action for moving forward the contained state machine.
902 /// This will lazily-allocate the delegate as needed.
904 Action MoveNextAction { get; }
906 /// <summary>Gets the state machine as a boxed object. This should only be used for debugging purposes.</summary>
907 IAsyncStateMachine GetStateMachineObject();
910 /// <summary>Shared helpers for manipulating state related to async state machines.</summary>
911 internal static class AsyncMethodBuilderCore // debugger depends on this exact name
913 /// <summary>Gets whether we should be tracking async method completions for eventing.</summary>
914 internal static bool TrackAsyncMethodCompletion
916 [MethodImpl(MethodImplOptions.AggressiveInlining)]
917 get => TplEtwProvider.Log.IsEnabled(EventLevel.Warning, TplEtwProvider.Keywords.AsyncMethod);
920 /// <summary>Gets a description of the state of the state machine object, suitable for debug purposes.</summary>
921 /// <param name="stateMachine">The state machine object.</param>
922 /// <returns>A description of the state machine.</returns>
923 internal static string GetAsyncStateMachineDescription(IAsyncStateMachine stateMachine)
925 Debug.Assert(stateMachine != null);
927 Type stateMachineType = stateMachine.GetType();
928 FieldInfo[] fields = stateMachineType.GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
930 var sb = new StringBuilder();
931 sb.AppendLine(stateMachineType.FullName);
932 foreach (FieldInfo fi in fields)
934 sb.AppendLine($" {fi.Name}: {fi.GetValue(stateMachine)}");
936 return sb.ToString();
939 internal static Action OutputAsyncCausalityEvents(Task task, Action continuation) =>
940 CreateContinuationWrapper(continuation, (innerContinuation, innerTask) =>
942 AsyncCausalityTracer.TraceSynchronousWorkStart(CausalityTraceLevel.Required, innerTask.Id, CausalitySynchronousWork.Execution);
943 innerContinuation.Invoke(); // Invoke the original continuation
944 AsyncCausalityTracer.TraceSynchronousWorkCompletion(CausalityTraceLevel.Required, CausalitySynchronousWork.Execution);
947 internal static Action CreateContinuationWrapper(Action continuation, Action<Action,Task> invokeAction, Task innerTask) =>
948 new ContinuationWrapper(continuation, invokeAction, innerTask).Invoke;
950 internal static Action TryGetStateMachineForDebugger(Action action) // debugger depends on this exact name/signature
952 object target = action.Target;
954 target is IAsyncStateMachineBox sm ? sm.GetStateMachineObject().MoveNext :
955 target is ContinuationWrapper cw ? TryGetStateMachineForDebugger(cw._continuation) :
959 internal static Task TryGetContinuationTask(Action continuation) =>
960 (continuation?.Target as ContinuationWrapper)?._innerTask;
962 /// <summary>Throws the exception on the ThreadPool.</summary>
963 /// <param name="exception">The exception to propagate.</param>
964 /// <param name="targetContext">The target context on which to propagate the exception. Null to use the ThreadPool.</param>
965 internal static void ThrowAsync(Exception exception, SynchronizationContext targetContext)
967 // Capture the exception into an ExceptionDispatchInfo so that its
968 // stack trace and Watson bucket info will be preserved
969 var edi = ExceptionDispatchInfo.Capture(exception);
971 // If the user supplied a SynchronizationContext...
972 if (targetContext != null)
976 // Post the throwing of the exception to that context, and return.
977 targetContext.Post(state => ((ExceptionDispatchInfo)state).Throw(), edi);
980 catch (Exception postException)
982 // If something goes horribly wrong in the Post, we'll
983 // propagate both exceptions on the ThreadPool
984 edi = ExceptionDispatchInfo.Capture(new AggregateException(exception, postException));
988 // If we have the new error reporting APIs, report this error. Otherwise, Propagate the exception(s) on the ThreadPool
989 #if FEATURE_COMINTEROP
990 if (!WindowsRuntimeMarshal.ReportUnhandledError(edi.SourceException))
991 #endif // FEATURE_COMINTEROP
993 ThreadPool.QueueUserWorkItem(state => ((ExceptionDispatchInfo)state).Throw(), edi);
998 /// Logically we pass just an Action (delegate) to a task for its action to 'ContinueWith' when it completes.
999 /// However debuggers and profilers need more information about what that action is. (In particular what
1000 /// the action after that is and after that. To solve this problem we create a 'ContinuationWrapper
1001 /// which when invoked just does the original action (the invoke action), but also remembers other information
1002 /// (like the action after that (which is also a ContinuationWrapper and thus form a linked list).
1003 // We also store that task if the action is associate with at task.
1005 private sealed class ContinuationWrapper
1007 private readonly Action<Action, Task> _invokeAction; // This wrapper is an action that wraps another action, this is that Action.
1008 internal readonly Action _continuation; // This is continuation which will happen after m_invokeAction (and is probably a ContinuationWrapper)
1009 internal readonly Task _innerTask; // If the continuation is logically going to invoke a task, this is that task (may be null)
1011 internal ContinuationWrapper(Action continuation, Action<Action, Task> invokeAction, Task innerTask)
1013 Debug.Assert(continuation != null, "Expected non-null continuation");
1014 Debug.Assert(invokeAction != null, "Expected non-null continuation");
1016 _invokeAction = invokeAction;
1017 _continuation = continuation;
1018 _innerTask = innerTask ?? TryGetContinuationTask(continuation); // if we don't have a task, see if our continuation is a wrapper and use that.
1021 internal void Invoke() => _invokeAction(_continuation, _innerTask);