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 using System.Threading;
18 using System.Threading.Tasks;
20 using Internal.Runtime.CompilerServices;
21 using System.Diagnostics.CodeAnalysis;
23 namespace System.Runtime.CompilerServices
26 /// Provides a builder for asynchronous methods that return void.
27 /// This type is intended for compiler use only.
29 public struct AsyncVoidMethodBuilder
31 /// <summary>The synchronization context associated with this operation.</summary>
32 private SynchronizationContext? _synchronizationContext;
33 /// <summary>The builder this void builder wraps.</summary>
34 private AsyncTaskMethodBuilder _builder; // mutable struct: must not be readonly
36 /// <summary>Initializes a new <see cref="AsyncVoidMethodBuilder"/>.</summary>
37 /// <returns>The initialized <see cref="AsyncVoidMethodBuilder"/>.</returns>
38 public static AsyncVoidMethodBuilder Create()
40 SynchronizationContext? sc = SynchronizationContext.Current;
41 sc?.OperationStarted();
43 // ProjectN's AsyncTaskMethodBuilder.Create() currently does additional debugger-related
44 // work, so we need to delegate to it.
45 return new AsyncVoidMethodBuilder() { _synchronizationContext = sc, _builder = AsyncTaskMethodBuilder.Create() };
47 // _builder should be initialized to AsyncTaskMethodBuilder.Create(), but on coreclr
48 // that Create() is a nop, so we can just return the default here.
49 return new AsyncVoidMethodBuilder() { _synchronizationContext = sc };
53 /// <summary>Initiates the builder's execution with the associated state machine.</summary>
54 /// <typeparam name="TStateMachine">Specifies the type of the state machine.</typeparam>
55 /// <param name="stateMachine">The state machine instance, passed by reference.</param>
56 /// <exception cref="System.ArgumentNullException">The <paramref name="stateMachine"/> argument was null (Nothing in Visual Basic).</exception>
58 [MethodImpl(MethodImplOptions.AggressiveInlining)]
59 public void Start<TStateMachine>(ref TStateMachine stateMachine) where TStateMachine : IAsyncStateMachine =>
60 AsyncMethodBuilderCore.Start(ref stateMachine);
62 /// <summary>Associates the builder with the state machine it represents.</summary>
63 /// <param name="stateMachine">The heap-allocated state machine object.</param>
64 /// <exception cref="System.ArgumentNullException">The <paramref name="stateMachine"/> argument was null (Nothing in Visual Basic).</exception>
65 /// <exception cref="System.InvalidOperationException">The builder is incorrectly initialized.</exception>
66 public void SetStateMachine(IAsyncStateMachine stateMachine) =>
67 _builder.SetStateMachine(stateMachine);
70 /// Schedules the specified state machine to be pushed forward when the specified awaiter completes.
72 /// <typeparam name="TAwaiter">Specifies the type of the awaiter.</typeparam>
73 /// <typeparam name="TStateMachine">Specifies the type of the state machine.</typeparam>
74 /// <param name="awaiter">The awaiter.</param>
75 /// <param name="stateMachine">The state machine.</param>
76 public void AwaitOnCompleted<TAwaiter, TStateMachine>(
77 ref TAwaiter awaiter, ref TStateMachine stateMachine)
78 where TAwaiter : INotifyCompletion
79 where TStateMachine : IAsyncStateMachine =>
80 _builder.AwaitOnCompleted(ref awaiter, ref stateMachine);
83 /// Schedules the specified state machine to be pushed forward when the specified awaiter completes.
85 /// <typeparam name="TAwaiter">Specifies the type of the awaiter.</typeparam>
86 /// <typeparam name="TStateMachine">Specifies the type of the state machine.</typeparam>
87 /// <param name="awaiter">The awaiter.</param>
88 /// <param name="stateMachine">The state machine.</param>
89 public void AwaitUnsafeOnCompleted<TAwaiter, TStateMachine>(
90 ref TAwaiter awaiter, ref TStateMachine stateMachine)
91 where TAwaiter : ICriticalNotifyCompletion
92 where TStateMachine : IAsyncStateMachine =>
93 _builder.AwaitUnsafeOnCompleted(ref awaiter, ref stateMachine);
95 /// <summary>Completes the method builder successfully.</summary>
96 public void SetResult()
98 if (AsyncCausalityTracer.LoggingOn)
100 AsyncCausalityTracer.TraceOperationCompletion(this.Task, AsyncCausalityStatus.Completed);
103 // Mark the builder as completed. As this is a void-returning method, this mostly
104 // doesn't matter, but it can affect things like debug events related to finalization.
105 _builder.SetResult();
107 if (_synchronizationContext != null)
109 NotifySynchronizationContextOfCompletion();
113 /// <summary>Faults the method builder with an exception.</summary>
114 /// <param name="exception">The exception that is the cause of this fault.</param>
115 /// <exception cref="System.ArgumentNullException">The <paramref name="exception"/> argument is null (Nothing in Visual Basic).</exception>
116 /// <exception cref="System.InvalidOperationException">The builder is not initialized.</exception>
117 public void SetException(Exception exception)
119 if (exception == null)
121 ThrowHelper.ThrowArgumentNullException(ExceptionArgument.exception);
124 if (AsyncCausalityTracer.LoggingOn)
126 AsyncCausalityTracer.TraceOperationCompletion(this.Task, AsyncCausalityStatus.Error);
129 if (_synchronizationContext != null)
131 // If we captured a synchronization context, Post the throwing of the exception to it
132 // and decrement its outstanding operation count.
135 System.Threading.Tasks.Task.ThrowAsync(exception, targetContext: _synchronizationContext);
139 NotifySynchronizationContextOfCompletion();
144 // Otherwise, queue the exception to be thrown on the ThreadPool. This will
145 // result in a crash unless legacy exception behavior is enabled by a config
146 // file or a CLR host.
147 System.Threading.Tasks.Task.ThrowAsync(exception, targetContext: null);
150 // The exception was propagated already; we don't need or want to fault the builder, just mark it as completed.
151 _builder.SetResult();
154 /// <summary>Notifies the current synchronization context that the operation completed.</summary>
155 private void NotifySynchronizationContextOfCompletion()
157 Debug.Assert(_synchronizationContext != null, "Must only be used with a non-null context.");
160 _synchronizationContext.OperationCompleted();
162 catch (Exception exc)
164 // If the interaction with the SynchronizationContext goes awry,
165 // fall back to propagating on the ThreadPool.
166 Task.ThrowAsync(exc, targetContext: null);
170 /// <summary>Lazily instantiate the Task in a non-thread-safe manner.</summary>
171 private Task Task => _builder.Task;
174 /// Gets an object that may be used to uniquely identify this builder to the debugger.
177 /// This property lazily instantiates the ID in a non-thread-safe manner.
178 /// It must only be used by the debugger and AsyncCausalityTracer in a single-threaded manner.
180 internal object ObjectIdForDebugger => _builder.ObjectIdForDebugger;
184 /// Provides a builder for asynchronous methods that return <see cref="System.Threading.Tasks.Task"/>.
185 /// This type is intended for compiler use only.
188 /// AsyncTaskMethodBuilder is a value type, and thus it is copied by value.
189 /// Prior to being copied, one of its Task, SetResult, or SetException members must be accessed,
190 /// or else the copies may end up building distinct Task instances.
192 public struct AsyncTaskMethodBuilder
194 /// <summary>A cached VoidTaskResult task used for builders that complete synchronously.</summary>
196 private static readonly Task<VoidTaskResult> s_cachedCompleted = AsyncTaskCache.CreateCacheableTask<VoidTaskResult>(default(VoidTaskResult));
198 private static readonly Task<VoidTaskResult> s_cachedCompleted = AsyncTaskMethodBuilder<VoidTaskResult>.s_defaultResultTask;
201 /// <summary>The generic builder object to which this non-generic instance delegates.</summary>
202 private AsyncTaskMethodBuilder<VoidTaskResult> m_builder; // mutable struct: must not be readonly. Debugger depends on the exact name of this field.
204 /// <summary>Initializes a new <see cref="AsyncTaskMethodBuilder"/>.</summary>
205 /// <returns>The initialized <see cref="AsyncTaskMethodBuilder"/>.</returns>
206 public static AsyncTaskMethodBuilder Create() =>
208 // ProjectN's AsyncTaskMethodBuilder<VoidTaskResult>.Create() currently does additional debugger-related
209 // work, so we need to delegate to it.
210 new AsyncTaskMethodBuilder { m_builder = AsyncTaskMethodBuilder<VoidTaskResult>.Create() };
212 // m_builder should be initialized to AsyncTaskMethodBuilder<VoidTaskResult>.Create(), but on coreclr
213 // that Create() is a nop, so we can just return the default here.
217 /// <summary>Initiates the builder's execution with the associated state machine.</summary>
218 /// <typeparam name="TStateMachine">Specifies the type of the state machine.</typeparam>
219 /// <param name="stateMachine">The state machine instance, passed by reference.</param>
220 [DebuggerStepThrough]
221 [MethodImpl(MethodImplOptions.AggressiveInlining)]
222 public void Start<TStateMachine>(ref TStateMachine stateMachine) where TStateMachine : IAsyncStateMachine =>
223 AsyncMethodBuilderCore.Start(ref stateMachine);
225 /// <summary>Associates the builder with the state machine it represents.</summary>
226 /// <param name="stateMachine">The heap-allocated state machine object.</param>
227 /// <exception cref="System.ArgumentNullException">The <paramref name="stateMachine"/> argument was null (Nothing in Visual Basic).</exception>
228 /// <exception cref="System.InvalidOperationException">The builder is incorrectly initialized.</exception>
229 public void SetStateMachine(IAsyncStateMachine stateMachine) =>
230 m_builder.SetStateMachine(stateMachine);
233 /// Schedules the specified state machine to be pushed forward when the specified awaiter completes.
235 /// <typeparam name="TAwaiter">Specifies the type of the awaiter.</typeparam>
236 /// <typeparam name="TStateMachine">Specifies the type of the state machine.</typeparam>
237 /// <param name="awaiter">The awaiter.</param>
238 /// <param name="stateMachine">The state machine.</param>
239 public void AwaitOnCompleted<TAwaiter, TStateMachine>(
240 ref TAwaiter awaiter, ref TStateMachine stateMachine)
241 where TAwaiter : INotifyCompletion
242 where TStateMachine : IAsyncStateMachine =>
243 m_builder.AwaitOnCompleted(ref awaiter, ref stateMachine);
246 /// Schedules the specified state machine to be pushed forward when the specified awaiter completes.
248 /// <typeparam name="TAwaiter">Specifies the type of the awaiter.</typeparam>
249 /// <typeparam name="TStateMachine">Specifies the type of the state machine.</typeparam>
250 /// <param name="awaiter">The awaiter.</param>
251 /// <param name="stateMachine">The state machine.</param>
252 public void AwaitUnsafeOnCompleted<TAwaiter, TStateMachine>(
253 ref TAwaiter awaiter, ref TStateMachine stateMachine)
254 where TAwaiter : ICriticalNotifyCompletion
255 where TStateMachine : IAsyncStateMachine =>
256 m_builder.AwaitUnsafeOnCompleted(ref awaiter, ref stateMachine);
258 /// <summary>Gets the <see cref="System.Threading.Tasks.Task"/> for this builder.</summary>
259 /// <returns>The <see cref="System.Threading.Tasks.Task"/> representing the builder's asynchronous operation.</returns>
260 /// <exception cref="System.InvalidOperationException">The builder is not initialized.</exception>
263 [MethodImpl(MethodImplOptions.AggressiveInlining)]
264 get => m_builder.Task;
268 /// Completes the <see cref="System.Threading.Tasks.Task"/> in the
269 /// <see cref="System.Threading.Tasks.TaskStatus">RanToCompletion</see> state.
271 /// <exception cref="System.InvalidOperationException">The builder is not initialized.</exception>
272 /// <exception cref="System.InvalidOperationException">The task has already completed.</exception>
273 public void SetResult() => m_builder.SetResult(s_cachedCompleted); // Using s_cachedCompleted is faster than using s_defaultResultTask.
276 /// Completes the <see cref="System.Threading.Tasks.Task"/> in the
277 /// <see cref="System.Threading.Tasks.TaskStatus">Faulted</see> state with the specified exception.
279 /// <param name="exception">The <see cref="System.Exception"/> to use to fault the task.</param>
280 /// <exception cref="System.ArgumentNullException">The <paramref name="exception"/> argument is null (Nothing in Visual Basic).</exception>
281 /// <exception cref="System.InvalidOperationException">The builder is not initialized.</exception>
282 /// <exception cref="System.InvalidOperationException">The task has already completed.</exception>
283 public void SetException(Exception exception) => m_builder.SetException(exception);
286 /// Called by the debugger to request notification when the first wait operation
287 /// (await, Wait, Result, etc.) on this builder's task completes.
289 /// <param name="enabled">
290 /// true to enable notification; false to disable a previously set notification.
292 internal void SetNotificationForWaitCompletion(bool enabled) => m_builder.SetNotificationForWaitCompletion(enabled);
295 /// Gets an object that may be used to uniquely identify this builder to the debugger.
298 /// This property lazily instantiates the ID in a non-thread-safe manner.
299 /// It must only be used by the debugger and tracing purposes, and only in a single-threaded manner
300 /// when no other threads are in the middle of accessing this property or this.Task.
302 internal object ObjectIdForDebugger => m_builder.ObjectIdForDebugger;
306 /// Provides a builder for asynchronous methods that return <see cref="System.Threading.Tasks.Task{TResult}"/>.
307 /// This type is intended for compiler use only.
310 /// AsyncTaskMethodBuilder{TResult} is a value type, and thus it is copied by value.
311 /// Prior to being copied, one of its Task, SetResult, or SetException members must be accessed,
312 /// or else the copies may end up building distinct Task instances.
314 public struct AsyncTaskMethodBuilder<TResult>
317 /// <summary>A cached task for default(TResult).</summary>
318 internal readonly static Task<TResult> s_defaultResultTask = AsyncTaskCache.CreateCacheableTask<TResult>(default);
321 /// <summary>The lazily-initialized built task.</summary>
322 private Task<TResult> m_task; // lazily-initialized: must not be readonly. Debugger depends on the exact name of this field.
324 /// <summary>Initializes a new <see cref="AsyncTaskMethodBuilder"/>.</summary>
325 /// <returns>The initialized <see cref="AsyncTaskMethodBuilder"/>.</returns>
326 public static AsyncTaskMethodBuilder<TResult> Create()
329 var result = new AsyncTaskMethodBuilder<TResult>();
330 if (System.Threading.Tasks.Task.s_asyncDebuggingEnabled)
332 // This allows the debugger to access m_task directly without evaluating ObjectIdForDebugger for ProjectN
333 result.InitializeTaskAsStateMachineBox();
337 // NOTE: If this method is ever updated to perform more initialization,
338 // other Create methods like AsyncTaskMethodBuilder.Create and
339 // AsyncValueTaskMethodBuilder.Create must be updated to call this.
344 /// <summary>Initiates the builder's execution with the associated state machine.</summary>
345 /// <typeparam name="TStateMachine">Specifies the type of the state machine.</typeparam>
346 /// <param name="stateMachine">The state machine instance, passed by reference.</param>
347 [DebuggerStepThrough]
348 [MethodImpl(MethodImplOptions.AggressiveInlining)]
349 public void Start<TStateMachine>(ref TStateMachine stateMachine) where TStateMachine : IAsyncStateMachine =>
350 AsyncMethodBuilderCore.Start(ref stateMachine);
352 /// <summary>Associates the builder with the state machine it represents.</summary>
353 /// <param name="stateMachine">The heap-allocated state machine object.</param>
354 /// <exception cref="System.ArgumentNullException">The <paramref name="stateMachine"/> argument was null (Nothing in Visual Basic).</exception>
355 /// <exception cref="System.InvalidOperationException">The builder is incorrectly initialized.</exception>
356 public void SetStateMachine(IAsyncStateMachine stateMachine)
357 => AsyncMethodBuilderCore.SetStateMachine(stateMachine, m_task);
360 /// Schedules the specified state machine to be pushed forward when the specified awaiter completes.
362 /// <typeparam name="TAwaiter">Specifies the type of the awaiter.</typeparam>
363 /// <typeparam name="TStateMachine">Specifies the type of the state machine.</typeparam>
364 /// <param name="awaiter">The awaiter.</param>
365 /// <param name="stateMachine">The state machine.</param>
366 public void AwaitOnCompleted<TAwaiter, TStateMachine>(
367 ref TAwaiter awaiter, ref TStateMachine stateMachine)
368 where TAwaiter : INotifyCompletion
369 where TStateMachine : IAsyncStateMachine
373 awaiter.OnCompleted(GetStateMachineBox(ref stateMachine).MoveNextAction);
377 System.Threading.Tasks.Task.ThrowAsync(e, targetContext: null);
382 /// Schedules the specified state machine to be pushed forward when the specified awaiter completes.
384 /// <typeparam name="TAwaiter">Specifies the type of the awaiter.</typeparam>
385 /// <typeparam name="TStateMachine">Specifies the type of the state machine.</typeparam>
386 /// <param name="awaiter">The awaiter.</param>
387 /// <param name="stateMachine">The state machine.</param>
388 // AggressiveOptimization to workaround boxing allocations in Tier0 until: https://github.com/dotnet/coreclr/issues/14474
389 [MethodImpl(MethodImplOptions.AggressiveOptimization)]
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 a ref type.
400 if ((null != (object)default(TAwaiter)!) && (awaiter is ITaskAwaiter)) // TODO-NULLABLE: default(T) == null warning (https://github.com/dotnet/roslyn/issues/34757)
402 ref TaskAwaiter ta = ref Unsafe.As<TAwaiter, TaskAwaiter>(ref awaiter); // relies on TaskAwaiter/TaskAwaiter<T> having the same layout
403 TaskAwaiter.UnsafeOnCompletedInternal(ta.m_task, box, continueOnCapturedContext: true);
405 else if ((null != (object)default(TAwaiter)!) && (awaiter is IConfiguredTaskAwaiter)) // TODO-NULLABLE: default(T) == null warning (https://github.com/dotnet/roslyn/issues/34757)
407 ref ConfiguredTaskAwaitable.ConfiguredTaskAwaiter ta = ref Unsafe.As<TAwaiter, ConfiguredTaskAwaitable.ConfiguredTaskAwaiter>(ref awaiter);
408 TaskAwaiter.UnsafeOnCompletedInternal(ta.m_task, box, ta.m_continueOnCapturedContext);
410 else if ((null != (object)default(TAwaiter)!) && (awaiter is IStateMachineBoxAwareAwaiter)) // TODO-NULLABLE: default(T) == null warning (https://github.com/dotnet/roslyn/issues/34757)
414 ((IStateMachineBoxAwareAwaiter)awaiter).AwaitUnsafeOnCompleted(box);
418 // Whereas with Task the code that hooks up and invokes the continuation is all local to corelib,
419 // with ValueTaskAwaiter we may be calling out to an arbitrary implementation of IValueTaskSource
420 // wrapped in the ValueTask, and as such we protect against errant exceptions that may emerge.
421 // We don't want such exceptions propagating back into the async method, which can't handle
422 // exceptions well at that location in the state machine, especially if the exception may occur
423 // after the ValueTaskAwaiter already successfully hooked up the callback, in which case it's possible
424 // two different flows of execution could end up happening in the same async method call.
425 System.Threading.Tasks.Task.ThrowAsync(e, targetContext: null);
430 // The awaiter isn't specially known. Fall back to doing a normal await.
433 awaiter.UnsafeOnCompleted(box.MoveNextAction);
437 System.Threading.Tasks.Task.ThrowAsync(e, targetContext: null);
442 /// <summary>Gets the "boxed" state machine object.</summary>
443 /// <typeparam name="TStateMachine">Specifies the type of the async state machine.</typeparam>
444 /// <param name="stateMachine">The state machine.</param>
445 /// <returns>The "boxed" state machine.</returns>
446 private IAsyncStateMachineBox GetStateMachineBox<TStateMachine>(
447 ref TStateMachine stateMachine)
448 where TStateMachine : IAsyncStateMachine
450 ExecutionContext? currentContext = ExecutionContext.Capture();
452 // Check first for the most common case: not the first yield in an async method.
453 // In this case, the first yield will have already "boxed" the state machine in
454 // a strongly-typed manner into an AsyncStateMachineBox. It will already contain
455 // the state machine as well as a MoveNextDelegate and a context. The only thing
456 // we might need to do is update the context if that's changed since it was stored.
457 if (m_task is AsyncStateMachineBox<TStateMachine> stronglyTypedBox)
459 if (stronglyTypedBox.Context != currentContext)
461 stronglyTypedBox.Context = currentContext;
463 return stronglyTypedBox;
466 // The least common case: we have a weakly-typed boxed. This results if the debugger
467 // or some other use of reflection accesses a property like ObjectIdForDebugger or a
468 // method like SetNotificationForWaitCompletion prior to the first await happening. In
469 // such situations, we need to get an object to represent the builder, but we don't yet
470 // know the type of the state machine, and thus can't use TStateMachine. Instead, we
471 // use the IAsyncStateMachine interface, which all TStateMachines implement. This will
472 // result in a boxing allocation when storing the TStateMachine if it's a struct, but
473 // this only happens in active debugging scenarios where such performance impact doesn't
475 if (m_task is AsyncStateMachineBox<IAsyncStateMachine> weaklyTypedBox)
477 // If this is the first await, we won't yet have a state machine, so store it.
478 if (weaklyTypedBox.StateMachine == null)
480 Debugger.NotifyOfCrossThreadDependency(); // same explanation as with usage below
481 weaklyTypedBox.StateMachine = stateMachine;
484 // Update the context. This only happens with a debugger, so no need to spend
485 // extra IL checking for equality before doing the assignment.
486 weaklyTypedBox.Context = currentContext;
487 return weaklyTypedBox;
490 // Alert a listening debugger that we can't make forward progress unless it slips threads.
491 // If we don't do this, and a method that uses "await foo;" is invoked through funceval,
492 // we could end up hooking up a callback to push forward the async method's state machine,
493 // the debugger would then abort the funceval after it takes too long, and then continuing
494 // execution could result in another callback being hooked up. At that point we have
495 // multiple callbacks registered to push the state machine, which could result in bad behavior.
496 Debugger.NotifyOfCrossThreadDependency();
498 // At this point, m_task should really be null, in which case we want to create the box.
499 // However, in a variety of debugger-related (erroneous) situations, it might be non-null,
500 // e.g. if the Task property is examined in a Watch window, forcing it to be lazily-intialized
501 // as a Task<TResult> rather than as an AsyncStateMachineBox. The worst that happens in such
502 // cases is we lose the ability to properly step in the debugger, as the debugger uses that
503 // object's identity to track this specific builder/state machine. As such, we proceed to
504 // overwrite whatever's there anyway, even if it's non-null.
506 // DebugFinalizableAsyncStateMachineBox looks like a small type, but it actually is not because
507 // it will have a copy of all the slots from its parent. It will add another hundred(s) bytes
508 // per each async method in CoreRT / ProjectN binaries without adding much value. Avoid
509 // generating this extra code until a better solution is implemented.
510 var box = new AsyncStateMachineBox<TStateMachine>();
512 var box = AsyncMethodBuilderCore.TrackAsyncMethodCompletion ?
513 CreateDebugFinalizableAsyncStateMachineBox<TStateMachine>() :
514 new AsyncStateMachineBox<TStateMachine>();
516 m_task = box; // important: this must be done before storing stateMachine into box.StateMachine!
517 box.StateMachine = stateMachine;
518 box.Context = currentContext;
520 // Log the creation of the state machine box object / task for this async method.
521 if (AsyncCausalityTracer.LoggingOn)
523 AsyncCausalityTracer.TraceOperationCreation(box, "Async: " + stateMachine.GetType().Name);
526 // And if async debugging is enabled, track the task.
527 if (System.Threading.Tasks.Task.s_asyncDebuggingEnabled)
529 System.Threading.Tasks.Task.AddToActiveTasks(box);
536 // Avoid forcing the JIT to build DebugFinalizableAsyncStateMachineBox<TStateMachine> unless it's actually needed.
537 [MethodImpl(MethodImplOptions.NoInlining)]
538 private static AsyncStateMachineBox<TStateMachine> CreateDebugFinalizableAsyncStateMachineBox<TStateMachine>()
539 where TStateMachine : IAsyncStateMachine =>
540 new DebugFinalizableAsyncStateMachineBox<TStateMachine>();
543 /// Provides an async state machine box with a finalizer that will fire an EventSource
544 /// event about the state machine if it's being finalized without having been completed.
546 /// <typeparam name="TStateMachine">Specifies the type of the state machine.</typeparam>
547 private sealed class DebugFinalizableAsyncStateMachineBox<TStateMachine> : // SOS DumpAsync command depends on this name
548 AsyncStateMachineBox<TStateMachine>
549 where TStateMachine : IAsyncStateMachine
551 ~DebugFinalizableAsyncStateMachineBox()
553 // If the state machine is being finalized, something went wrong during its processing,
554 // e.g. it awaited something that got collected without itself having been completed.
555 // Fire an event with details about the state machine to help with debugging.
556 if (!IsCompleted) // double-check it's not completed, just to help minimize false positives
558 TplEventSource.Log.IncompleteAsyncMethod(this);
564 /// <summary>A strongly-typed box for Task-based async state machines.</summary>
565 /// <typeparam name="TStateMachine">Specifies the type of the state machine.</typeparam>
566 private class AsyncStateMachineBox<TStateMachine> : // SOS DumpAsync command depends on this name
567 Task<TResult>, IAsyncStateMachineBox
568 where TStateMachine : IAsyncStateMachine
570 /// <summary>Delegate used to invoke on an ExecutionContext when passed an instance of this box type.</summary>
571 private static readonly ContextCallback s_callback = ExecutionContextCallback;
573 // Used to initialize s_callback above. We don't use a lambda for this on purpose: a lambda would
574 // introduce a new generic type behind the scenes that comes with a hefty size penalty in AOT builds.
575 private static void ExecutionContextCallback(object? s)
577 Debug.Assert(s is AsyncStateMachineBox<TStateMachine>);
578 // Only used privately to pass directly to EC.Run
579 Unsafe.As<AsyncStateMachineBox<TStateMachine>>(s).StateMachine!.MoveNext();
582 /// <summary>A delegate to the <see cref="MoveNext()"/> method.</summary>
583 private Action? _moveNextAction;
584 /// <summary>The state machine itself.</summary>
585 [AllowNull, MaybeNull] public TStateMachine StateMachine = default; // mutable struct; do not make this readonly. SOS DumpAsync command depends on this name.
586 /// <summary>Captured ExecutionContext with which to invoke <see cref="MoveNextAction"/>; may be null.</summary>
587 public ExecutionContext? Context;
589 /// <summary>A delegate to the <see cref="MoveNext()"/> method.</summary>
590 public Action MoveNextAction => _moveNextAction ?? (_moveNextAction = new Action(MoveNext));
592 internal sealed override void ExecuteFromThreadPool(Thread threadPoolThread) => MoveNext(threadPoolThread);
594 /// <summary>Calls MoveNext on <see cref="StateMachine"/></summary>
595 public void MoveNext() => MoveNext(threadPoolThread: null);
597 private void MoveNext(Thread? threadPoolThread)
599 Debug.Assert(!IsCompleted);
601 bool loggingOn = AsyncCausalityTracer.LoggingOn;
604 AsyncCausalityTracer.TraceSynchronousWorkStart(this, CausalitySynchronousWork.Execution);
607 ExecutionContext? context = Context;
610 Debug.Assert(StateMachine != null);
611 StateMachine.MoveNext();
615 if (threadPoolThread is null)
617 ExecutionContext.RunInternal(context, s_callback, this);
621 ExecutionContext.RunFromThreadPoolDispatchLoop(threadPoolThread, context, s_callback, this);
627 // If async debugging is enabled, remove the task from tracking.
628 if (System.Threading.Tasks.Task.s_asyncDebuggingEnabled)
630 System.Threading.Tasks.Task.RemoveFromActiveTasks(this);
633 // Clear out state now that the async method has completed.
634 // This avoids keeping arbitrary state referenced by lifted locals
635 // if this Task / state machine box is held onto.
636 StateMachine = default;
640 // In case this is a state machine box with a finalizer, suppress its finalization
641 // as it's now complete. We only need the finalizer to run if the box is collected
642 // without having been completed.
643 if (AsyncMethodBuilderCore.TrackAsyncMethodCompletion)
645 GC.SuppressFinalize(this);
652 AsyncCausalityTracer.TraceSynchronousWorkCompletion(CausalitySynchronousWork.Execution);
656 /// <summary>Gets the state machine as a boxed object. This should only be used for debugging purposes.</summary>
657 IAsyncStateMachine IAsyncStateMachineBox.GetStateMachineObject() => StateMachine!; // likely boxes, only use for debugging
660 /// <summary>Gets the <see cref="System.Threading.Tasks.Task{TResult}"/> for this builder.</summary>
661 /// <returns>The <see cref="System.Threading.Tasks.Task{TResult}"/> representing the builder's asynchronous operation.</returns>
662 public Task<TResult> Task
664 [MethodImpl(MethodImplOptions.AggressiveInlining)]
665 get => m_task ?? InitializeTaskAsPromise();
669 /// Initializes the task, which must not yet be initialized. Used only when the Task is being forced into
670 /// existence when no state machine is needed, e.g. when the builder is being synchronously completed with
671 /// an exception, when the builder is being used out of the context of an async method, etc.
673 [MethodImpl(MethodImplOptions.NoInlining)]
674 private Task<TResult> InitializeTaskAsPromise()
676 Debug.Assert(m_task == null);
677 return (m_task = new Task<TResult>());
681 /// Initializes the task, which must not yet be initialized. Used only when the Task is being forced into
682 /// existence due to the debugger trying to enable step-out/step-over/etc. prior to the first await yielding
683 /// in an async method. In that case, we don't know the actual TStateMachine type, so we're forced to
684 /// use IAsyncStateMachine instead.
686 [MethodImpl(MethodImplOptions.NoInlining)]
687 private Task<TResult> InitializeTaskAsStateMachineBox()
689 Debug.Assert(m_task == null);
691 // DebugFinalizableAsyncStateMachineBox looks like a small type, but it actually is not because
692 // it will have a copy of all the slots from its parent. It will add another hundred(s) bytes
693 // per each async method in CoreRT / ProjectN binaries without adding much value. Avoid
694 // generating this extra code until a better solution is implemented.
695 return (m_task = new AsyncStateMachineBox<IAsyncStateMachine>());
697 return (m_task = AsyncMethodBuilderCore.TrackAsyncMethodCompletion ?
698 CreateDebugFinalizableAsyncStateMachineBox<IAsyncStateMachine>() :
699 new AsyncStateMachineBox<IAsyncStateMachine>());
704 /// Completes the <see cref="System.Threading.Tasks.Task{TResult}"/> in the
705 /// <see cref="System.Threading.Tasks.TaskStatus">RanToCompletion</see> state with the specified result.
707 /// <param name="result">The result to use to complete the task.</param>
708 /// <exception cref="System.InvalidOperationException">The task has already completed.</exception>
709 public void SetResult(TResult result)
711 // Get the currently stored task, which will be non-null if get_Task has already been accessed.
712 // If there isn't one, get a task and store it.
715 m_task = GetTaskForResult(result);
716 Debug.Assert(m_task != null, $"{nameof(GetTaskForResult)} should never return null");
720 // Slow path: complete the existing task.
721 SetExistingTaskResult(result);
725 /// <summary>Completes the already initialized task with the specified result.</summary>
726 /// <param name="result">The result to use to complete the task.</param>
727 private void SetExistingTaskResult([AllowNull] TResult result)
729 Debug.Assert(m_task != null, "Expected non-null task");
731 if (AsyncCausalityTracer.LoggingOn)
733 AsyncCausalityTracer.TraceOperationCompletion(m_task, AsyncCausalityStatus.Completed);
736 if (!m_task.TrySetResult(result))
738 ThrowHelper.ThrowInvalidOperationException(ExceptionResource.TaskT_TransitionToFinal_AlreadyCompleted);
743 /// Completes the builder by using either the supplied completed task, or by completing
744 /// the builder's previously accessed task using default(TResult).
746 /// <param name="completedTask">A task already completed with the value default(TResult).</param>
747 /// <exception cref="System.InvalidOperationException">The task has already completed.</exception>
748 internal void SetResult(Task<TResult> completedTask)
750 Debug.Assert(completedTask != null, "Expected non-null task");
751 Debug.Assert(completedTask.IsCompletedSuccessfully, "Expected a successfully completed task");
753 // Get the currently stored task, which will be non-null if get_Task has already been accessed.
754 // If there isn't one, store the supplied completed task.
757 m_task = completedTask;
761 // Otherwise, complete the task that's there.
762 SetExistingTaskResult(default!); // Remove ! when nullable attributes are respected
767 /// Completes the <see cref="System.Threading.Tasks.Task{TResult}"/> in the
768 /// <see cref="System.Threading.Tasks.TaskStatus">Faulted</see> state with the specified exception.
770 /// <param name="exception">The <see cref="System.Exception"/> to use to fault the task.</param>
771 /// <exception cref="System.ArgumentNullException">The <paramref name="exception"/> argument is null (Nothing in Visual Basic).</exception>
772 /// <exception cref="System.InvalidOperationException">The task has already completed.</exception>
773 public void SetException(Exception exception)
775 if (exception == null)
777 ThrowHelper.ThrowArgumentNullException(ExceptionArgument.exception);
780 // Get the task, forcing initialization if it hasn't already been initialized.
781 Task<TResult> task = this.Task;
783 // If the exception represents cancellation, cancel the task. Otherwise, fault the task.
784 bool successfullySet = exception is OperationCanceledException oce ?
785 task.TrySetCanceled(oce.CancellationToken, oce) :
786 task.TrySetException(exception);
788 // Unlike with TaskCompletionSource, we do not need to spin here until _taskAndStateMachine is completed,
789 // since AsyncTaskMethodBuilder.SetException should not be immediately followed by any code
790 // that depends on the task having completely completed. Moreover, with correct usage,
791 // SetResult or SetException should only be called once, so the Try* methods should always
792 // return true, so no spinning would be necessary anyway (the spinning in TCS is only relevant
793 // if another thread completes the task first).
794 if (!successfullySet)
796 ThrowHelper.ThrowInvalidOperationException(ExceptionResource.TaskT_TransitionToFinal_AlreadyCompleted);
801 /// Called by the debugger to request notification when the first wait operation
802 /// (await, Wait, Result, etc.) on this builder's task completes.
804 /// <param name="enabled">
805 /// true to enable notification; false to disable a previously set notification.
808 /// This should only be invoked from within an asynchronous method,
809 /// and only by the debugger.
811 internal void SetNotificationForWaitCompletion(bool enabled)
813 // Get the task (forcing initialization if not already initialized), and set debug notification
814 (m_task ?? InitializeTaskAsStateMachineBox()).SetNotificationForWaitCompletion(enabled);
816 // NOTE: It's important that the debugger use builder.SetNotificationForWaitCompletion
817 // rather than builder.Task.SetNotificationForWaitCompletion. Even though the latter will
818 // lazily-initialize the task as well, it'll initialize it to a Task<T> (which is important
819 // to minimize size for cases where an ATMB is used directly by user code to avoid the
820 // allocation overhead of a TaskCompletionSource). If that's done prior to the first await,
821 // the GetMoveNextDelegate code, which needs an AsyncStateMachineBox, will end up creating
822 // a new box and overwriting the previously created task. That'll change the object identity
823 // of the task being used for wait completion notification, and no notification will
824 // ever arrive, breaking step-out behavior when stepping out before the first yielding await.
828 /// Gets an object that may be used to uniquely identify this builder to the debugger.
831 /// This property lazily instantiates the ID in a non-thread-safe manner.
832 /// It must only be used by the debugger and tracing purposes, and only in a single-threaded manner
833 /// when no other threads are in the middle of accessing this or other members that lazily initialize the task.
835 internal object ObjectIdForDebugger => m_task ?? InitializeTaskAsStateMachineBox();
838 /// Gets a task for the specified result. This will either
839 /// be a cached or new task, never null.
841 /// <param name="result">The result for which we need a task.</param>
842 /// <returns>The completed task containing the result.</returns>
843 [MethodImpl(MethodImplOptions.AggressiveInlining)] // method looks long, but for a given TResult it results in a relatively small amount of asm
844 internal static Task<TResult> GetTaskForResult(TResult result)
847 // Currently NUTC does not perform the optimization needed by this method. The result is that
848 // every call to this method results in quite a lot of work, including many allocations, which
849 // is the opposite of the intent. For now, let's just return a new Task each time.
850 // Bug 719350 tracks re-optimizing this in ProjectN.
852 // The goal of this function is to be give back a cached task if possible,
853 // or to otherwise give back a new task. To give back a cached task,
854 // we need to be able to evaluate the incoming result value, and we need
855 // to avoid as much overhead as possible when doing so, as this function
856 // is invoked as part of the return path from every async method.
857 // Most tasks won't be cached, and thus we need the checks for those that are
858 // to be as close to free as possible. This requires some trickiness given the
859 // lack of generic specialization in .NET.
861 // Be very careful when modifying this code. It has been tuned
862 // to comply with patterns recognized by both 32-bit and 64-bit JITs.
863 // If changes are made here, be sure to look at the generated assembly, as
864 // small tweaks can have big consequences for what does and doesn't get optimized away.
866 // Note that this code only ever accesses a static field when it knows it'll
867 // find a cached value, since static fields (even if readonly and integral types)
868 // require special access helpers in this NGEN'd and domain-neutral.
870 if (null != (object)default(TResult)!) // help the JIT avoid the value type branches for ref types // TODO-NULLABLE: default(T) == null warning (https://github.com/dotnet/roslyn/issues/34757)
872 // Special case simple value types:
880 // As of .NET 4.5, the (Type)(object)result pattern used below
881 // is recognized and optimized by both 32-bit and 64-bit JITs.
883 // For Boolean, we cache all possible values.
884 if (typeof(TResult) == typeof(bool)) // only the relevant branches are kept for each value-type generic instantiation
886 bool value = (bool)(object)result!;
887 Task<bool> task = value ? AsyncTaskCache.TrueTask : AsyncTaskCache.FalseTask;
888 return Unsafe.As<Task<TResult>>(task); // UnsafeCast avoids type check we know will succeed
890 // For Int32, we cache a range of common values, e.g. [-1,9).
891 else if (typeof(TResult) == typeof(int))
893 // Compare to constants to avoid static field access if outside of cached range.
894 // We compare to the upper bound first, as we're more likely to cache miss on the upper side than on the
895 // lower side, due to positive values being more common than negative as return values.
896 int value = (int)(object)result!;
897 if (value < AsyncTaskCache.EXCLUSIVE_INT32_MAX &&
898 value >= AsyncTaskCache.INCLUSIVE_INT32_MIN)
900 Task<int> task = AsyncTaskCache.Int32Tasks[value - AsyncTaskCache.INCLUSIVE_INT32_MIN];
901 return Unsafe.As<Task<TResult>>(task); // UnsafeCast avoids a type check we know will succeed
904 // For other known value types, we only special-case 0 / default(TResult).
906 (typeof(TResult) == typeof(uint) && default == (uint)(object)result!) ||
907 (typeof(TResult) == typeof(byte) && default(byte) == (byte)(object)result!) ||
908 (typeof(TResult) == typeof(sbyte) && default(sbyte) == (sbyte)(object)result!) ||
909 (typeof(TResult) == typeof(char) && default(char) == (char)(object)result!) ||
910 (typeof(TResult) == typeof(long) && default == (long)(object)result!) ||
911 (typeof(TResult) == typeof(ulong) && default == (ulong)(object)result!) ||
912 (typeof(TResult) == typeof(short) && default(short) == (short)(object)result!) ||
913 (typeof(TResult) == typeof(ushort) && default(ushort) == (ushort)(object)result!) ||
914 (typeof(TResult) == typeof(IntPtr) && default == (IntPtr)(object)result!) ||
915 (typeof(TResult) == typeof(UIntPtr) && default == (UIntPtr)(object)result!))
917 return s_defaultResultTask;
920 else if (result == null) // optimized away for value types
922 return s_defaultResultTask;
926 // No cached task is available. Manufacture a new one for this result.
927 return new Task<TResult>(result);
931 /// <summary>Provides a cache of closed generic tasks for async methods.</summary>
932 internal static class AsyncTaskCache
935 // All static members are initialized inline to ensure type is beforefieldinit
937 /// <summary>A cached Task{Boolean}.Result == true.</summary>
938 internal readonly static Task<bool> TrueTask = CreateCacheableTask(true);
939 /// <summary>A cached Task{Boolean}.Result == false.</summary>
940 internal readonly static Task<bool> FalseTask = CreateCacheableTask(false);
942 /// <summary>The cache of Task{Int32}.</summary>
943 internal readonly static Task<int>[] Int32Tasks = CreateInt32Tasks();
944 /// <summary>The minimum value, inclusive, for which we want a cached task.</summary>
945 internal const int INCLUSIVE_INT32_MIN = -1;
946 /// <summary>The maximum value, exclusive, for which we want a cached task.</summary>
947 internal const int EXCLUSIVE_INT32_MAX = 9;
948 /// <summary>Creates an array of cached tasks for the values in the range [INCLUSIVE_MIN,EXCLUSIVE_MAX).</summary>
949 private static Task<int>[] CreateInt32Tasks()
951 Debug.Assert(EXCLUSIVE_INT32_MAX >= INCLUSIVE_INT32_MIN, "Expected max to be at least min");
952 var tasks = new Task<int>[EXCLUSIVE_INT32_MAX - INCLUSIVE_INT32_MIN];
953 for (int i = 0; i < tasks.Length; i++)
955 tasks[i] = CreateCacheableTask(i + INCLUSIVE_INT32_MIN);
961 /// <summary>Creates a non-disposable task.</summary>
962 /// <typeparam name="TResult">Specifies the result type.</typeparam>
963 /// <param name="result">The result for the task.</param>
964 /// <returns>The cacheable task.</returns>
965 internal static Task<TResult> CreateCacheableTask<TResult>([AllowNull] TResult result) =>
966 new Task<TResult>(false, result, (TaskCreationOptions)InternalTaskOptions.DoNotDispose, default);
970 /// An interface implemented by all <see cref="AsyncTaskMethodBuilder{TResult}.AsyncStateMachineBox{TStateMachine}"/> instances, regardless of generics.
972 internal interface IAsyncStateMachineBox
974 /// <summary>Move the state machine forward.</summary>
978 /// Gets an action for moving forward the contained state machine.
979 /// This will lazily-allocate the delegate as needed.
981 Action MoveNextAction { get; }
983 /// <summary>Gets the state machine as a boxed object. This should only be used for debugging purposes.</summary>
984 IAsyncStateMachine GetStateMachineObject();
987 /// <summary>Shared helpers for manipulating state related to async state machines.</summary>
988 internal static class AsyncMethodBuilderCore // debugger depends on this exact name
990 /// <summary>Initiates the builder's execution with the associated state machine.</summary>
991 /// <typeparam name="TStateMachine">Specifies the type of the state machine.</typeparam>
992 /// <param name="stateMachine">The state machine instance, passed by reference.</param>
993 [DebuggerStepThrough]
994 public static void Start<TStateMachine>(ref TStateMachine stateMachine) where TStateMachine : IAsyncStateMachine
996 if (stateMachine == null) // TStateMachines are generally non-nullable value types, so this check will be elided
998 ThrowHelper.ThrowArgumentNullException(ExceptionArgument.stateMachine);
1001 // enregistrer variables with 0 post-fix so they can be used in registers without EH forcing them to stack
1002 // Capture references to Thread Contexts
1003 Thread currentThread0 = Thread.CurrentThread;
1004 Thread currentThread = currentThread0;
1005 ExecutionContext? previousExecutionCtx0 = currentThread0._executionContext;
1007 // Store current ExecutionContext and SynchronizationContext as "previousXxx".
1008 // This allows us to restore them and undo any Context changes made in stateMachine.MoveNext
1009 // so that they won't "leak" out of the first await.
1010 ExecutionContext? previousExecutionCtx = previousExecutionCtx0;
1011 SynchronizationContext? previousSyncCtx = currentThread0._synchronizationContext;
1015 stateMachine.MoveNext();
1019 // Re-enregistrer variables post EH with 1 post-fix so they can be used in registers rather than from stack
1020 SynchronizationContext? previousSyncCtx1 = previousSyncCtx;
1021 Thread currentThread1 = currentThread;
1022 // The common case is that these have not changed, so avoid the cost of a write barrier if not needed.
1023 if (previousSyncCtx1 != currentThread1._synchronizationContext)
1025 // Restore changed SynchronizationContext back to previous
1026 currentThread1._synchronizationContext = previousSyncCtx1;
1029 ExecutionContext? previousExecutionCtx1 = previousExecutionCtx;
1030 ExecutionContext? currentExecutionCtx1 = currentThread1._executionContext;
1031 if (previousExecutionCtx1 != currentExecutionCtx1)
1033 ExecutionContext.RestoreChangedContextToThread(currentThread1, previousExecutionCtx1, currentExecutionCtx1);
1038 public static void SetStateMachine(IAsyncStateMachine stateMachine, Task task)
1040 if (stateMachine == null)
1042 ThrowHelper.ThrowArgumentNullException(ExceptionArgument.stateMachine);
1047 ThrowHelper.ThrowInvalidOperationException(ExceptionResource.AsyncMethodBuilder_InstanceNotInitialized);
1050 // SetStateMachine was originally needed in order to store the boxed state machine reference into
1051 // the boxed copy. Now that a normal box is no longer used, SetStateMachine is also legacy. We need not
1052 // do anything here, and thus assert to ensure we're not calling this from our own implementations.
1053 Debug.Fail("SetStateMachine should not be used.");
1057 /// <summary>Gets whether we should be tracking async method completions for eventing.</summary>
1058 internal static bool TrackAsyncMethodCompletion
1060 [MethodImpl(MethodImplOptions.AggressiveInlining)]
1061 get => TplEventSource.Log.IsEnabled(EventLevel.Warning, TplEventSource.Keywords.AsyncMethod);
1065 /// <summary>Gets a description of the state of the state machine object, suitable for debug purposes.</summary>
1066 /// <param name="stateMachine">The state machine object.</param>
1067 /// <returns>A description of the state machine.</returns>
1068 internal static string GetAsyncStateMachineDescription(IAsyncStateMachine stateMachine)
1070 Debug.Assert(stateMachine != null);
1072 Type stateMachineType = stateMachine.GetType();
1073 FieldInfo[] fields = stateMachineType.GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
1075 var sb = new StringBuilder();
1076 sb.AppendLine(stateMachineType.FullName);
1077 foreach (FieldInfo fi in fields)
1079 sb.AppendLine($" {fi.Name}: {fi.GetValue(stateMachine)}");
1081 return sb.ToString();
1084 internal static Action CreateContinuationWrapper(Action continuation, Action<Action, Task> invokeAction, Task innerTask) =>
1085 new ContinuationWrapper(continuation, invokeAction, innerTask).Invoke;
1087 /// <summary>This helper routine is targeted by the debugger. Its purpose is to remove any delegate wrappers introduced by
1088 /// the framework that the debugger doesn't want to see.</summary>
1090 [DependencyReductionRoot]
1092 internal static Action TryGetStateMachineForDebugger(Action action) // debugger depends on this exact name/signature
1094 object? target = action.Target;
1096 target is IAsyncStateMachineBox sm ? sm.GetStateMachineObject().MoveNext :
1097 target is ContinuationWrapper cw ? TryGetStateMachineForDebugger(cw._continuation) :
1101 internal static Task? TryGetContinuationTask(Action continuation) =>
1102 (continuation.Target is ContinuationWrapper wrapper) ?
1103 wrapper._innerTask : // A wrapped continuation, created by an awaiter
1104 continuation.Target as Task; // The continuation targets a task directly, such as with AsyncStateMachineBox
1107 /// Logically we pass just an Action (delegate) to a task for its action to 'ContinueWith' when it completes.
1108 /// However debuggers and profilers need more information about what that action is. (In particular what
1109 /// the action after that is and after that. To solve this problem we create a 'ContinuationWrapper
1110 /// which when invoked just does the original action (the invoke action), but also remembers other information
1111 /// (like the action after that (which is also a ContinuationWrapper and thus form a linked list).
1112 /// We also store that task if the action is associate with at task.
1114 private sealed class ContinuationWrapper // SOS DumpAsync command depends on this name
1116 private readonly Action<Action, Task> _invokeAction; // This wrapper is an action that wraps another action, this is that Action.
1117 internal readonly Action _continuation; // This is continuation which will happen after m_invokeAction (and is probably a ContinuationWrapper). SOS DumpAsync command depends on this name.
1118 internal readonly Task _innerTask; // If the continuation is logically going to invoke a task, this is that task (may be null)
1120 internal ContinuationWrapper(Action continuation, Action<Action, Task> invokeAction, Task innerTask)
1122 Debug.Assert(continuation != null, "Expected non-null continuation");
1123 Debug.Assert(invokeAction != null, "Expected non-null invokeAction");
1124 Debug.Assert(innerTask != null, "Expected non-null innerTask");
1126 _invokeAction = invokeAction;
1127 _continuation = continuation;
1128 _innerTask = innerTask;
1131 internal void Invoke() => _invokeAction(_continuation, _innerTask);