Enable ETW/EventSource logging of task IDs for boxed state machines (#27115) (#27217)
[platform/upstream/coreclr.git] / src / System.Private.CoreLib / shared / System / Runtime / CompilerServices / AsyncMethodBuilder.cs
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.
4
5 // =+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
6 //
7 //
8 //
9 // Compiler-targeted types that build tasks for use as the return types of asynchronous methods.
10 //
11 // =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
12
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;
19 using System.Text;
20 using Internal.Runtime.CompilerServices;
21 using System.Diagnostics.CodeAnalysis;
22
23 namespace System.Runtime.CompilerServices
24 {
25     /// <summary>
26     /// Provides a builder for asynchronous methods that return void.
27     /// This type is intended for compiler use only.
28     /// </summary>
29     public struct AsyncVoidMethodBuilder
30     {
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
35
36         /// <summary>Initializes a new <see cref="AsyncVoidMethodBuilder"/>.</summary>
37         /// <returns>The initialized <see cref="AsyncVoidMethodBuilder"/>.</returns>
38         public static AsyncVoidMethodBuilder Create()
39         {
40             SynchronizationContext? sc = SynchronizationContext.Current;
41             sc?.OperationStarted();
42 #if PROJECTN
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() };
46 #else
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 };
50 #endif
51         }
52
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>
57         [DebuggerStepThrough]
58         [MethodImpl(MethodImplOptions.AggressiveInlining)]
59         public void Start<TStateMachine>(ref TStateMachine stateMachine) where TStateMachine : IAsyncStateMachine =>
60             AsyncMethodBuilderCore.Start(ref stateMachine);
61
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);
68
69         /// <summary>
70         /// Schedules the specified state machine to be pushed forward when the specified awaiter completes.
71         /// </summary>
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);
81
82         /// <summary>
83         /// Schedules the specified state machine to be pushed forward when the specified awaiter completes.
84         /// </summary>
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);
94
95         /// <summary>Completes the method builder successfully.</summary>
96         public void SetResult()
97         {
98             if (AsyncCausalityTracer.LoggingOn)
99             {
100                 AsyncCausalityTracer.TraceOperationCompletion(this.Task, AsyncCausalityStatus.Completed);
101             }
102
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();
106
107             if (_synchronizationContext != null)
108             {
109                 NotifySynchronizationContextOfCompletion();
110             }
111         }
112
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)
118         {
119             if (exception == null)
120             {
121                 ThrowHelper.ThrowArgumentNullException(ExceptionArgument.exception);
122             }
123
124             if (AsyncCausalityTracer.LoggingOn)
125             {
126                 AsyncCausalityTracer.TraceOperationCompletion(this.Task, AsyncCausalityStatus.Error);
127             }
128
129             if (_synchronizationContext != null)
130             {
131                 // If we captured a synchronization context, Post the throwing of the exception to it 
132                 // and decrement its outstanding operation count.
133                 try
134                 {
135                     System.Threading.Tasks.Task.ThrowAsync(exception, targetContext: _synchronizationContext);
136                 }
137                 finally
138                 {
139                     NotifySynchronizationContextOfCompletion();
140                 }
141             }
142             else
143             {
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);
148             }
149
150             // The exception was propagated already; we don't need or want to fault the builder, just mark it as completed.
151             _builder.SetResult();
152         }
153
154         /// <summary>Notifies the current synchronization context that the operation completed.</summary>
155         private void NotifySynchronizationContextOfCompletion()
156         {
157             Debug.Assert(_synchronizationContext != null, "Must only be used with a non-null context.");
158             try
159             {
160                 _synchronizationContext.OperationCompleted();
161             }
162             catch (Exception exc)
163             {
164                 // If the interaction with the SynchronizationContext goes awry,
165                 // fall back to propagating on the ThreadPool.
166                 Task.ThrowAsync(exc, targetContext: null);
167             }
168         }
169
170         /// <summary>Lazily instantiate the Task in a non-thread-safe manner.</summary>
171         private Task Task => _builder.Task;
172
173         /// <summary>
174         /// Gets an object that may be used to uniquely identify this builder to the debugger.
175         /// </summary>
176         /// <remarks>
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.
179         /// </remarks>
180         internal object ObjectIdForDebugger => _builder.ObjectIdForDebugger;
181     }
182
183     /// <summary>
184     /// Provides a builder for asynchronous methods that return <see cref="System.Threading.Tasks.Task"/>.
185     /// This type is intended for compiler use only.
186     /// </summary>
187     /// <remarks>
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.
191     /// </remarks>
192     public struct AsyncTaskMethodBuilder
193     {
194         /// <summary>A cached VoidTaskResult task used for builders that complete synchronously.</summary>
195 #if PROJECTN
196         private static readonly Task<VoidTaskResult> s_cachedCompleted = AsyncTaskCache.CreateCacheableTask<VoidTaskResult>(default(VoidTaskResult));
197 #else
198         private static readonly Task<VoidTaskResult> s_cachedCompleted = AsyncTaskMethodBuilder<VoidTaskResult>.s_defaultResultTask;
199 #endif
200
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.
203
204         /// <summary>Initializes a new <see cref="AsyncTaskMethodBuilder"/>.</summary>
205         /// <returns>The initialized <see cref="AsyncTaskMethodBuilder"/>.</returns>
206         public static AsyncTaskMethodBuilder Create() =>
207 #if PROJECTN
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() };
211 #else
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.
214             default;
215 #endif
216
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);
224
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);
231
232         /// <summary>
233         /// Schedules the specified state machine to be pushed forward when the specified awaiter completes.
234         /// </summary>
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);
244
245         /// <summary>
246         /// Schedules the specified state machine to be pushed forward when the specified awaiter completes.
247         /// </summary>
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);
257
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>
261         public Task Task
262         {
263             [MethodImpl(MethodImplOptions.AggressiveInlining)]
264             get => m_builder.Task;
265         }
266
267         /// <summary>
268         /// Completes the <see cref="System.Threading.Tasks.Task"/> in the 
269         /// <see cref="System.Threading.Tasks.TaskStatus">RanToCompletion</see> state.
270         /// </summary>
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.
274
275         /// <summary>
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.
278         /// </summary>
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);
284
285         /// <summary>
286         /// Called by the debugger to request notification when the first wait operation
287         /// (await, Wait, Result, etc.) on this builder's task completes.
288         /// </summary>
289         /// <param name="enabled">
290         /// true to enable notification; false to disable a previously set notification.
291         /// </param>
292         internal void SetNotificationForWaitCompletion(bool enabled) => m_builder.SetNotificationForWaitCompletion(enabled);
293
294         /// <summary>
295         /// Gets an object that may be used to uniquely identify this builder to the debugger.
296         /// </summary>
297         /// <remarks>
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.
301         /// </remarks>
302         internal object ObjectIdForDebugger => m_builder.ObjectIdForDebugger;
303     }
304
305     /// <summary>
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.
308     /// </summary>
309     /// <remarks>
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.
313     /// </remarks>
314     public struct AsyncTaskMethodBuilder<TResult>
315     {
316 #if !PROJECTN
317         /// <summary>A cached task for default(TResult).</summary>
318         internal readonly static Task<TResult> s_defaultResultTask = AsyncTaskCache.CreateCacheableTask<TResult>(default);
319 #endif
320
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.
323
324         /// <summary>Initializes a new <see cref="AsyncTaskMethodBuilder"/>.</summary>
325         /// <returns>The initialized <see cref="AsyncTaskMethodBuilder"/>.</returns>
326         public static AsyncTaskMethodBuilder<TResult> Create()
327         {
328 #if PROJECTN
329             var result = new AsyncTaskMethodBuilder<TResult>();
330             if (System.Threading.Tasks.Task.s_asyncDebuggingEnabled)
331             {
332                 // This allows the debugger to access m_task directly without evaluating ObjectIdForDebugger for ProjectN
333                 result.InitializeTaskAsStateMachineBox();
334             }            
335             return result;
336 #else
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.
340             return default;
341 #endif
342         }
343
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);
351
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);
358
359         /// <summary>
360         /// Schedules the specified state machine to be pushed forward when the specified awaiter completes.
361         /// </summary>
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
370         {
371             try
372             {
373                 awaiter.OnCompleted(GetStateMachineBox(ref stateMachine).MoveNextAction);
374             }
375             catch (Exception e)
376             {
377                 System.Threading.Tasks.Task.ThrowAsync(e, targetContext: null);
378             }
379         }
380
381         /// <summary>
382         /// Schedules the specified state machine to be pushed forward when the specified awaiter completes.
383         /// </summary>
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
394         {
395             IAsyncStateMachineBox box = GetStateMachineBox(ref stateMachine);
396
397             // The null tests here ensure that the jit can optimize away the interface
398             // tests when TAwaiter is a ref type.
399
400             if ((null != (object)default(TAwaiter)!) && (awaiter is ITaskAwaiter)) // TODO-NULLABLE: default(T) == null warning (https://github.com/dotnet/roslyn/issues/34757)
401             {
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);
404             }
405             else if ((null != (object)default(TAwaiter)!) && (awaiter is IConfiguredTaskAwaiter)) // TODO-NULLABLE: default(T) == null warning (https://github.com/dotnet/roslyn/issues/34757)
406             {
407                 ref ConfiguredTaskAwaitable.ConfiguredTaskAwaiter ta = ref Unsafe.As<TAwaiter, ConfiguredTaskAwaitable.ConfiguredTaskAwaiter>(ref awaiter);
408                 TaskAwaiter.UnsafeOnCompletedInternal(ta.m_task, box, ta.m_continueOnCapturedContext);
409             }
410             else if ((null != (object)default(TAwaiter)!) && (awaiter is IStateMachineBoxAwareAwaiter)) // TODO-NULLABLE: default(T) == null warning (https://github.com/dotnet/roslyn/issues/34757)
411             {
412                 try
413                 {
414                     ((IStateMachineBoxAwareAwaiter)awaiter).AwaitUnsafeOnCompleted(box);
415                 }
416                 catch (Exception e)
417                 {
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);
426                 }
427             }
428             else
429             {
430                 // The awaiter isn't specially known. Fall back to doing a normal await.
431                 try
432                 {
433                     awaiter.UnsafeOnCompleted(box.MoveNextAction);
434                 }
435                 catch (Exception e)
436                 {
437                     System.Threading.Tasks.Task.ThrowAsync(e, targetContext: null);
438                 }
439             }
440         }
441
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
449         {
450             ExecutionContext? currentContext = ExecutionContext.Capture();
451
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)
458             {
459                 if (stronglyTypedBox.Context != currentContext)
460                 {
461                     stronglyTypedBox.Context = currentContext;
462                 }
463                 return stronglyTypedBox;
464             }
465
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
474             // matter.
475             if (m_task is AsyncStateMachineBox<IAsyncStateMachine> weaklyTypedBox)
476             {
477                 // If this is the first await, we won't yet have a state machine, so store it.
478                 if (weaklyTypedBox.StateMachine == null)
479                 {
480                     Debugger.NotifyOfCrossThreadDependency(); // same explanation as with usage below
481                     weaklyTypedBox.StateMachine = stateMachine;
482                 }
483
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;
488             }
489
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();
497
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.
505 #if CORERT
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>();
511 #else
512             var box = AsyncMethodBuilderCore.TrackAsyncMethodCompletion ?
513                 CreateDebugFinalizableAsyncStateMachineBox<TStateMachine>() :
514                 new AsyncStateMachineBox<TStateMachine>();
515 #endif
516             m_task = box; // important: this must be done before storing stateMachine into box.StateMachine!
517             box.StateMachine = stateMachine;
518             box.Context = currentContext;
519
520             // Log the creation of the state machine box object / task for this async method.
521             if (AsyncCausalityTracer.LoggingOn)
522             {
523                 AsyncCausalityTracer.TraceOperationCreation(box, "Async: " + stateMachine.GetType().Name);
524             }
525
526             // And if async debugging is enabled, track the task.
527             if (System.Threading.Tasks.Task.s_asyncDebuggingEnabled)
528             {
529                 System.Threading.Tasks.Task.AddToActiveTasks(box);
530             }
531
532             return box;
533         }
534
535 #if !CORERT
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>();
541
542         /// <summary>
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.
545         /// </summary>
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
550         {
551             ~DebugFinalizableAsyncStateMachineBox()
552             {
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
557                 {
558                     TplEventSource.Log.IncompleteAsyncMethod(this);
559                 }
560             }
561         }
562 #endif
563
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
569         {
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;
572
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)
576             {
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();
580             }
581
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;
588
589             /// <summary>A delegate to the <see cref="MoveNext()"/> method.</summary>
590             public Action MoveNextAction => _moveNextAction ?? (_moveNextAction = new Action(MoveNext));
591
592             internal sealed override void ExecuteFromThreadPool(Thread threadPoolThread) => MoveNext(threadPoolThread);
593
594             /// <summary>Calls MoveNext on <see cref="StateMachine"/></summary>
595             public void MoveNext() => MoveNext(threadPoolThread: null);
596
597             private void MoveNext(Thread? threadPoolThread)
598             {
599                 Debug.Assert(!IsCompleted);
600
601                 bool loggingOn = AsyncCausalityTracer.LoggingOn;
602                 if (loggingOn)
603                 {
604                     AsyncCausalityTracer.TraceSynchronousWorkStart(this, CausalitySynchronousWork.Execution);
605                 }
606
607                 ExecutionContext? context = Context;
608                 if (context == null)
609                 {
610                     Debug.Assert(StateMachine != null);
611                     StateMachine.MoveNext();
612                 }
613                 else
614                 {
615                     if (threadPoolThread is null)
616                     {
617                         ExecutionContext.RunInternal(context, s_callback, this);
618                     }
619                     else
620                     {
621                         ExecutionContext.RunFromThreadPoolDispatchLoop(threadPoolThread, context, s_callback, this);
622                     }
623                 }
624
625                 if (IsCompleted)
626                 {
627                     // If async debugging is enabled, remove the task from tracking.
628                     if (System.Threading.Tasks.Task.s_asyncDebuggingEnabled)
629                     {
630                         System.Threading.Tasks.Task.RemoveFromActiveTasks(this);
631                     }
632
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;
637                     Context = default;
638
639 #if !CORERT
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)
644                     {
645                         GC.SuppressFinalize(this);
646                     }
647 #endif
648                 }
649
650                 if (loggingOn)
651                 {
652                     AsyncCausalityTracer.TraceSynchronousWorkCompletion(CausalitySynchronousWork.Execution);
653                 }
654             }
655
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
658         }
659
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
663         {
664             [MethodImpl(MethodImplOptions.AggressiveInlining)]
665             get => m_task ?? InitializeTaskAsPromise();
666         }
667
668         /// <summary>
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.
672         /// </summary>
673         [MethodImpl(MethodImplOptions.NoInlining)]
674         private Task<TResult> InitializeTaskAsPromise()
675         {
676             Debug.Assert(m_task == null);
677             return (m_task = new Task<TResult>());
678         }
679
680         /// <summary>
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.
685         /// </summary>
686         [MethodImpl(MethodImplOptions.NoInlining)]
687         private Task<TResult> InitializeTaskAsStateMachineBox()
688         {
689             Debug.Assert(m_task == null);
690 #if CORERT
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>());
696 #else
697             return (m_task = AsyncMethodBuilderCore.TrackAsyncMethodCompletion ?
698                 CreateDebugFinalizableAsyncStateMachineBox<IAsyncStateMachine>() :
699                 new AsyncStateMachineBox<IAsyncStateMachine>());
700 #endif
701         }
702
703         /// <summary>
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.
706         /// </summary>
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)
710         {
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.
713             if (m_task == null)
714             {
715                 m_task = GetTaskForResult(result);
716                 Debug.Assert(m_task != null, $"{nameof(GetTaskForResult)} should never return null");
717             }
718             else
719             {
720                 // Slow path: complete the existing task.
721                 SetExistingTaskResult(result);
722             }
723         }
724
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)
728         {
729             Debug.Assert(m_task != null, "Expected non-null task");
730
731             if (AsyncCausalityTracer.LoggingOn)
732             {
733                 AsyncCausalityTracer.TraceOperationCompletion(m_task, AsyncCausalityStatus.Completed);
734             }
735
736             if (!m_task.TrySetResult(result))
737             {
738                 ThrowHelper.ThrowInvalidOperationException(ExceptionResource.TaskT_TransitionToFinal_AlreadyCompleted);
739             }
740         }
741
742         /// <summary>
743         /// Completes the builder by using either the supplied completed task, or by completing
744         /// the builder's previously accessed task using default(TResult).
745         /// </summary>
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)
749         {
750             Debug.Assert(completedTask != null, "Expected non-null task");
751             Debug.Assert(completedTask.IsCompletedSuccessfully, "Expected a successfully completed task");
752
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.
755             if (m_task == null)
756             {
757                 m_task = completedTask;
758             }
759             else
760             {
761                 // Otherwise, complete the task that's there.
762                 SetExistingTaskResult(default!); // Remove ! when nullable attributes are respected
763             }
764         }
765
766         /// <summary>
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.
769         /// </summary>
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)
774         {
775             if (exception == null)
776             {
777                 ThrowHelper.ThrowArgumentNullException(ExceptionArgument.exception);
778             }
779
780             // Get the task, forcing initialization if it hasn't already been initialized.
781             Task<TResult> task = this.Task;
782
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);
787
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)
795             {
796                 ThrowHelper.ThrowInvalidOperationException(ExceptionResource.TaskT_TransitionToFinal_AlreadyCompleted);
797             }
798         }
799
800         /// <summary>
801         /// Called by the debugger to request notification when the first wait operation
802         /// (await, Wait, Result, etc.) on this builder's task completes.
803         /// </summary>
804         /// <param name="enabled">
805         /// true to enable notification; false to disable a previously set notification.
806         /// </param>
807         /// <remarks>
808         /// This should only be invoked from within an asynchronous method,
809         /// and only by the debugger.
810         /// </remarks>
811         internal void SetNotificationForWaitCompletion(bool enabled)
812         {
813             // Get the task (forcing initialization if not already initialized), and set debug notification
814             (m_task ?? InitializeTaskAsStateMachineBox()).SetNotificationForWaitCompletion(enabled);
815
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.
825         }
826
827         /// <summary>
828         /// Gets an object that may be used to uniquely identify this builder to the debugger.
829         /// </summary>
830         /// <remarks>
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.
834         /// </remarks>
835         internal object ObjectIdForDebugger => m_task ?? InitializeTaskAsStateMachineBox();
836
837         /// <summary>
838         /// Gets a task for the specified result.  This will either
839         /// be a cached or new task, never null.
840         /// </summary>
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)
845         {
846 #if PROJECTN
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.
851 #else
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.
860             //
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.
865             //
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.
869
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)
871             {
872                 // Special case simple value types:
873                 // - Boolean
874                 // - Byte, SByte
875                 // - Char
876                 // - Int32, UInt32
877                 // - Int64, UInt64
878                 // - Int16, UInt16
879                 // - IntPtr, UIntPtr
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.
882
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
885                 {
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
889                 }
890                 // For Int32, we cache a range of common values, e.g. [-1,9).
891                 else if (typeof(TResult) == typeof(int))
892                 {
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)
899                     {
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
902                     }
903                 }
904                 // For other known value types, we only special-case 0 / default(TResult).
905                 else if (
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!))
916                 {
917                     return s_defaultResultTask;
918                 }
919             }
920             else if (result == null) // optimized away for value types
921             {
922                 return s_defaultResultTask;
923             }
924 #endif
925
926             // No cached task is available.  Manufacture a new one for this result.
927             return new Task<TResult>(result);
928         }
929     }
930
931     /// <summary>Provides a cache of closed generic tasks for async methods.</summary>
932     internal static class AsyncTaskCache
933     {
934 #if !PROJECTN
935         // All static members are initialized inline to ensure type is beforefieldinit
936
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);
941
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()
950         {
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++)
954             {
955                 tasks[i] = CreateCacheableTask(i + INCLUSIVE_INT32_MIN);
956             }
957             return tasks;
958         }
959 #endif
960
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);
967     }
968
969     /// <summary>
970     /// An interface implemented by all <see cref="AsyncTaskMethodBuilder{TResult}.AsyncStateMachineBox{TStateMachine}"/> instances, regardless of generics.
971     /// </summary>
972     internal interface IAsyncStateMachineBox
973     {
974         /// <summary>Move the state machine forward.</summary>
975         void MoveNext();
976
977         /// <summary>
978         /// Gets an action for moving forward the contained state machine.
979         /// This will lazily-allocate the delegate as needed.
980         /// </summary>
981         Action MoveNextAction { get; }
982
983         /// <summary>Gets the state machine as a boxed object.  This should only be used for debugging purposes.</summary>
984         IAsyncStateMachine GetStateMachineObject();
985     }
986
987     /// <summary>Shared helpers for manipulating state related to async state machines.</summary>
988     internal static class AsyncMethodBuilderCore // debugger depends on this exact name
989     {
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
995         {
996             if (stateMachine == null) // TStateMachines are generally non-nullable value types, so this check will be elided
997             {
998                 ThrowHelper.ThrowArgumentNullException(ExceptionArgument.stateMachine);
999             }
1000
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;
1006
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;
1012
1013             try
1014             {
1015                 stateMachine.MoveNext();
1016             }
1017             finally
1018             {
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)
1024                 {
1025                     // Restore changed SynchronizationContext back to previous
1026                     currentThread1._synchronizationContext = previousSyncCtx1;
1027                 }
1028
1029                 ExecutionContext? previousExecutionCtx1 = previousExecutionCtx;
1030                 ExecutionContext? currentExecutionCtx1 = currentThread1._executionContext;
1031                 if (previousExecutionCtx1 != currentExecutionCtx1)
1032                 {
1033                     ExecutionContext.RestoreChangedContextToThread(currentThread1, previousExecutionCtx1, currentExecutionCtx1);
1034                 }
1035             }
1036         }
1037
1038         public static void SetStateMachine(IAsyncStateMachine stateMachine, Task task)
1039         {
1040             if (stateMachine == null)
1041             {
1042                 ThrowHelper.ThrowArgumentNullException(ExceptionArgument.stateMachine);
1043             }
1044
1045             if (task != null)
1046             {
1047                 ThrowHelper.ThrowInvalidOperationException(ExceptionResource.AsyncMethodBuilder_InstanceNotInitialized);
1048             }
1049
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.");
1054         }
1055
1056 #if !CORERT
1057         /// <summary>Gets whether we should be tracking async method completions for eventing.</summary>
1058         internal static bool TrackAsyncMethodCompletion
1059         {
1060             [MethodImpl(MethodImplOptions.AggressiveInlining)]
1061             get => TplEventSource.Log.IsEnabled(EventLevel.Warning, TplEventSource.Keywords.AsyncMethod);
1062         }
1063 #endif
1064
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)
1069         {
1070             Debug.Assert(stateMachine != null);
1071
1072             Type stateMachineType = stateMachine.GetType();
1073             FieldInfo[] fields = stateMachineType.GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
1074
1075             var sb = new StringBuilder();
1076             sb.AppendLine(stateMachineType.FullName);
1077             foreach (FieldInfo fi in fields)
1078             {
1079                 sb.AppendLine($"    {fi.Name}: {fi.GetValue(stateMachine)}");
1080             }
1081             return sb.ToString();
1082         }
1083
1084         internal static Action CreateContinuationWrapper(Action continuation, Action<Action, Task> invokeAction, Task innerTask) =>
1085             new ContinuationWrapper(continuation, invokeAction, innerTask).Invoke;
1086
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>
1089 #if PROJECTN
1090         [DependencyReductionRoot]
1091 #endif
1092         internal static Action TryGetStateMachineForDebugger(Action action) // debugger depends on this exact name/signature
1093         {
1094             object? target = action.Target;
1095             return
1096                 target is IAsyncStateMachineBox sm ? sm.GetStateMachineObject().MoveNext :
1097                 target is ContinuationWrapper cw ? TryGetStateMachineForDebugger(cw._continuation) :
1098                 action;
1099         }
1100
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
1105
1106         /// <summary>
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.  
1113         /// </summary>
1114         private sealed class ContinuationWrapper // SOS DumpAsync command depends on this name
1115         {
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)
1119
1120             internal ContinuationWrapper(Action continuation, Action<Action, Task> invokeAction, Task innerTask)
1121             {
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");
1125
1126                 _invokeAction = invokeAction;
1127                 _continuation = continuation;
1128                 _innerTask = innerTask;
1129             }
1130
1131             internal void Invoke() => _invokeAction(_continuation, _innerTask);
1132         }
1133     }
1134 }