diff --git a/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/AsyncMethodBuilderCore.cs b/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/AsyncMethodBuilderCore.cs index 459fd26e6abb3..7cb85c290c755 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/AsyncMethodBuilderCore.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/AsyncMethodBuilderCore.cs @@ -102,6 +102,12 @@ internal static string GetAsyncStateMachineDescription(IAsyncStateMachine stateM return sb.ToString(); } + [MethodImpl(MethodImplOptions.NoInlining)] + internal static void LogTraceOperationBegin(Task t, Type stateMachineType) + { + TplEventSource.Log.TraceOperationBegin(t.Id, "Async: " + stateMachineType.Name, 0); + } + internal static Action CreateContinuationWrapper(Action continuation, Action invokeAction, Task innerTask) => new ContinuationWrapper(continuation, invokeAction, innerTask).Invoke; diff --git a/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/AsyncTaskMethodBuilderT.cs b/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/AsyncTaskMethodBuilderT.cs index c3064ad22114b..5a2ccb1d63549 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/AsyncTaskMethodBuilderT.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/AsyncTaskMethodBuilderT.cs @@ -157,6 +157,8 @@ public struct AsyncTaskMethodBuilder { ExecutionContext? currentContext = ExecutionContext.Capture(); + IAsyncStateMachineBox result; + // Check first for the most common case: not the first yield in an async method. // In this case, the first yield will have already "boxed" the state machine in // a strongly-typed manner into an AsyncStateMachineBox. It will already contain @@ -168,9 +170,8 @@ public struct AsyncTaskMethodBuilder { stronglyTypedBox.Context = currentContext; } - return stronglyTypedBox; + result = stronglyTypedBox; } - // The least common case: we have a weakly-typed boxed. This results if the debugger // or some other use of reflection accesses a property like ObjectIdForDebugger or a // method like SetNotificationForWaitCompletion prior to the first await happening. In @@ -180,7 +181,7 @@ public struct AsyncTaskMethodBuilder // result in a boxing allocation when storing the TStateMachine if it's a struct, but // this only happens in active debugging scenarios where such performance impact doesn't // matter. - if (taskField is AsyncStateMachineBox weaklyTypedBox) + else if (taskField is AsyncStateMachineBox weaklyTypedBox) { // If this is the first await, we won't yet have a state machine, so store it. if (weaklyTypedBox.StateMachine == null) @@ -192,52 +193,55 @@ public struct AsyncTaskMethodBuilder // Update the context. This only happens with a debugger, so no need to spend // extra IL checking for equality before doing the assignment. weaklyTypedBox.Context = currentContext; - return weaklyTypedBox; + result = weaklyTypedBox; } - - // Alert a listening debugger that we can't make forward progress unless it slips threads. - // If we don't do this, and a method that uses "await foo;" is invoked through funceval, - // we could end up hooking up a callback to push forward the async method's state machine, - // the debugger would then abort the funceval after it takes too long, and then continuing - // execution could result in another callback being hooked up. At that point we have - // multiple callbacks registered to push the state machine, which could result in bad behavior. - Debugger.NotifyOfCrossThreadDependency(); - - // At this point, taskField should really be null, in which case we want to create the box. - // However, in a variety of debugger-related (erroneous) situations, it might be non-null, - // e.g. if the Task property is examined in a Watch window, forcing it to be lazily-initialized - // as a Task rather than as an AsyncStateMachineBox. The worst that happens in such - // cases is we lose the ability to properly step in the debugger, as the debugger uses that - // object's identity to track this specific builder/state machine. As such, we proceed to - // overwrite whatever's there anyway, even if it's non-null. + else + { + // Alert a listening debugger that we can't make forward progress unless it slips threads. + // If we don't do this, and a method that uses "await foo;" is invoked through funceval, + // we could end up hooking up a callback to push forward the async method's state machine, + // the debugger would then abort the funceval after it takes too long, and then continuing + // execution could result in another callback being hooked up. At that point we have + // multiple callbacks registered to push the state machine, which could result in bad behavior. + Debugger.NotifyOfCrossThreadDependency(); + + // At this point, taskField should really be null, in which case we want to create the box. + // However, in a variety of debugger-related (erroneous) situations, it might be non-null, + // e.g. if the Task property is examined in a Watch window, forcing it to be lazily-initialized + // as a Task rather than as an AsyncStateMachineBox. The worst that happens in such + // cases is we lose the ability to properly step in the debugger, as the debugger uses that + // object's identity to track this specific builder/state machine. As such, we proceed to + // overwrite whatever's there anyway, even if it's non-null. #if NATIVEAOT - // DebugFinalizableAsyncStateMachineBox looks like a small type, but it actually is not because - // it will have a copy of all the slots from its parent. It will add another hundred(s) bytes - // per each async method in NativeAOT binaries without adding much value. Avoid - // generating this extra code until a better solution is implemented. - var box = new AsyncStateMachineBox(); + // DebugFinalizableAsyncStateMachineBox looks like a small type, but it actually is not because + // it will have a copy of all the slots from its parent. It will add another hundred(s) bytes + // per each async method in NativeAOT binaries without adding much value. Avoid + // generating this extra code until a better solution is implemented. + var box = new AsyncStateMachineBox(); #else - AsyncStateMachineBox box = AsyncMethodBuilderCore.TrackAsyncMethodCompletion ? - CreateDebugFinalizableAsyncStateMachineBox() : - new AsyncStateMachineBox(); + AsyncStateMachineBox box = AsyncMethodBuilderCore.TrackAsyncMethodCompletion ? + CreateDebugFinalizableAsyncStateMachineBox() : + new AsyncStateMachineBox(); #endif - taskField = box; // important: this must be done before storing stateMachine into box.StateMachine! - box.StateMachine = stateMachine; - box.Context = currentContext; + taskField = box; // important: this must be done before storing stateMachine into box.StateMachine! + box.StateMachine = stateMachine; + box.Context = currentContext; - // Log the creation of the state machine box object / task for this async method. - if (TplEventSource.Log.IsEnabled()) - { - TplEventSource.Log.TraceOperationBegin(box.Id, "Async: " + stateMachine.GetType().Name, 0); - } + // Log the creation of the state machine box object / task for this async method. + if (TplEventSource.Log.IsEnabled()) + { + AsyncMethodBuilderCore.LogTraceOperationBegin(box, stateMachine.GetType()); + } - // And if async debugging is enabled, track the task. - if (Threading.Tasks.Task.s_asyncDebuggingEnabled) - { - Threading.Tasks.Task.AddToActiveTasks(box); + // And if async debugging is enabled, track the task. + if (Threading.Tasks.Task.s_asyncDebuggingEnabled) + { + Threading.Tasks.Task.AddToActiveTasks(box); + } + result = box; } - return box; + return result; } #if !NATIVEAOT