From ce862829e500236b26a3dcb2eea63085d6cfd67b Mon Sep 17 00:00:00 2001 From: vsadov Date: Tue, 19 May 2026 18:01:19 -0700 Subject: [PATCH 1/7] use thunk helpers in Await api for ValueTask --- .../CompilerServices/AsyncHelpers.CoreCLR.cs | 2 +- .../Runtime/CompilerServices/AsyncHelpers.cs | 20 +++++++++++-------- 2 files changed, 13 insertions(+), 9 deletions(-) diff --git a/src/coreclr/System.Private.CoreLib/src/System/Runtime/CompilerServices/AsyncHelpers.CoreCLR.cs b/src/coreclr/System.Private.CoreLib/src/System/Runtime/CompilerServices/AsyncHelpers.CoreCLR.cs index d91895128a85ec..f54b1987852a6f 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/Runtime/CompilerServices/AsyncHelpers.CoreCLR.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/Runtime/CompilerServices/AsyncHelpers.CoreCLR.cs @@ -380,7 +380,7 @@ private static unsafe void TransparentAwaitValueTask(ValueTask valueTask) [BypassReadyToRun] [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.Async)] - private static unsafe void TransparentAwaitValueTaskOfT(ValueTask valueTask) + private static unsafe void TransparentAwaitValueTaskOfT(ValueTask valueTask) { ref RuntimeAsyncAwaitState state = ref t_runtimeAsyncAwaitState; Continuation? sentinelContinuation = state.SentinelContinuation ??= new Continuation(); diff --git a/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/AsyncHelpers.cs b/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/AsyncHelpers.cs index 510b863d4dda8e..b23e0df9f2f3ac 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/AsyncHelpers.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/AsyncHelpers.cs @@ -104,13 +104,16 @@ public static void Await(Task task) [StackTraceHidden] public static T Await(ValueTask task) { - ValueTaskAwaiter awaiter = task.GetAwaiter(); - if (!awaiter.IsCompleted) + if (task.IsCompleted) { - UnsafeAwaitAwaiter(awaiter); + return task.Result; } - return awaiter.GetResult(); + TailAwait(); + TransparentAwaitValueTaskOfT(task); + + // unreachable + while (true) ; } /// @@ -123,13 +126,14 @@ public static T Await(ValueTask task) [StackTraceHidden] public static void Await(ValueTask task) { - ValueTaskAwaiter awaiter = task.GetAwaiter(); - if (!awaiter.IsCompleted) + if (task.IsCompleted) { - UnsafeAwaitAwaiter(awaiter); + task.ThrowIfCompletedUnsuccessfully(); + return; } - awaiter.GetResult(); + TailAwait(); + TransparentAwaitValueTask(task); } /// From 749e29136a1285fb6a69e86a3fe62b7b04371b77 Mon Sep 17 00:00:00 2001 From: vsadov <8218165+VSadov@users.noreply.github.com> Date: Wed, 20 May 2026 04:16:59 -0700 Subject: [PATCH 2/7] Capture the context when calling through Await. --- .../CompilerServices/AsyncHelpers.CoreCLR.cs | 84 +++++++++++++++++-- .../AsyncHelpers.ValueTaskContinuation.cs | 8 ++ .../Runtime/CompilerServices/AsyncHelpers.cs | 40 +++++---- 3 files changed, 109 insertions(+), 23 deletions(-) diff --git a/src/coreclr/System.Private.CoreLib/src/System/Runtime/CompilerServices/AsyncHelpers.CoreCLR.cs b/src/coreclr/System.Private.CoreLib/src/System/Runtime/CompilerServices/AsyncHelpers.CoreCLR.cs index f54b1987852a6f..038989e90bc4ba 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/Runtime/CompilerServices/AsyncHelpers.CoreCLR.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/Runtime/CompilerServices/AsyncHelpers.CoreCLR.cs @@ -378,6 +378,38 @@ private static unsafe void TransparentAwaitValueTask(ValueTask valueTask) AsyncSuspend(vtsCont); } + // same as above, but with continuation context capture. + [BypassReadyToRun] + [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.Async)] + private static unsafe void AwaitValueTaskSource(object source, short token) + { + ref RuntimeAsyncAwaitState state = ref t_runtimeAsyncAwaitState; + Continuation? sentinelContinuation = state.SentinelContinuation ??= new Continuation(); + + ValueTaskContinuation? vtsCont = state.CachedValueTaskContinuation; + if (vtsCont != null) + { + state.CachedValueTaskContinuation = null; + } + else + { + vtsCont = new ValueTaskContinuation(); + } + + Debug.Assert(source != null); + vtsCont.Initialize(source, token); + + // capture continuation context. (this is the difference from the transparent helper) + // CONSIDER: We capture ContinuationContext for consistency, but we only need flags. + CaptureContinuationContext(ref vtsCont.ContinuationContext, ref vtsCont.Flags); + + sentinelContinuation.Next = vtsCont; + state.StackState->ValueTaskContinuation = vtsCont; + + state.CaptureContexts(); + AsyncSuspend(vtsCont); + } + [BypassReadyToRun] [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.Async)] private static unsafe void TransparentAwaitValueTaskOfT(ValueTask valueTask) @@ -405,6 +437,36 @@ private static unsafe void TransparentAwaitValueTaskOfT(ValueTask valueTas AsyncSuspend(vtsCont); } + [BypassReadyToRun] + [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.Async)] + private static unsafe void AwaitValueTaskSourceOfT(object source, short token) + { + ref RuntimeAsyncAwaitState state = ref t_runtimeAsyncAwaitState; + Continuation? sentinelContinuation = state.SentinelContinuation ??= new Continuation(); + + ValueTaskContinuation? vtsCont = state.CachedValueTaskContinuation; + if (vtsCont != null) + { + state.CachedValueTaskContinuation = null; + } + else + { + vtsCont = new ValueTaskContinuation(); + } + + Debug.Assert(source != null); + vtsCont.Initialize(source, token); + + // CONSIDER: We capture ContinuationContext for consistency, but we only need flags. + CaptureContinuationContext(ref vtsCont.ContinuationContext, ref vtsCont.Flags); + + sentinelContinuation.Next = vtsCont; + state.StackState->ValueTaskContinuation = vtsCont; + + state.CaptureContexts(); + AsyncSuspend(vtsCont); + } + /// /// Used by internal thunks that implement awaiting on Task. /// @@ -493,23 +555,25 @@ internal unsafe bool HandleSuspended(ref RuntimeAsyncAwaitState state) // Head continuation should be the result of async call to AwaitAwaiter or UnsafeAwaitAwaiter. // These never have special continuation context handling. + // Except for the scenario with ValueTaskContinuation that wraps ValueTaskSource + // which can capture continuation context. const ContinuationFlags continueFlags = ContinuationFlags.ContinueOnCapturedSynchronizationContext | ContinuationFlags.ContinueOnThreadPool | ContinuationFlags.ContinueOnCapturedTaskScheduler; - Debug.Assert((headContinuation.Flags & continueFlags) == 0); - SetContinuationState(headContinuation); try { if (stackState->CriticalNotifier is { } critNotifier) { + Debug.Assert((headContinuation.Flags & continueFlags) == 0); critNotifier.UnsafeOnCompleted(GetContinuationAction()); } else if (stackState->TaskNotifier is { } taskNotifier) { + Debug.Assert((headContinuation.Flags & continueFlags) == 0); // Runtime async callable wrapper for task returning // method. This implements the context transparent // forwarding and makes these wrappers minimal cost. @@ -525,6 +589,7 @@ internal unsafe bool HandleSuspended(ref RuntimeAsyncAwaitState state) Debug.Assert(source != null); if (source is Task t) { + Debug.Assert((headContinuation.Flags & continueFlags) == 0); if (!t.TryAddCompletionAction(this)) { ThreadPool.UnsafeQueueUserWorkItemInternal(this, preferLocal: true); @@ -541,17 +606,17 @@ internal unsafe bool HandleSuspended(ref RuntimeAsyncAwaitState state) // the continuation chain builds from the innermost frame out and at the time when the // notifier is created we do not know yet if the caller wants to continue on a context. - // Skip to a nontransparent/user continuation. Such continuaton must exist. + // Skip to a nontransparent/user continuation. Such continuation must exist. // Since we see a VTS notifier, something was directly or indirectly // awaiting an async thunk for a ValueTask-returning method. // That can only happen in nontransparent/user code. - Continuation nextUserContinuation = valueTaskSourceCont.Next!; - while ((nextUserContinuation.Flags & continueFlags) == 0 && nextUserContinuation.Next != null) + Continuation contWithContinueFlags = valueTaskSourceCont; + while ((contWithContinueFlags.Flags & continueFlags) == 0 && contWithContinueFlags.Next != null) { - nextUserContinuation = nextUserContinuation.Next; + contWithContinueFlags = contWithContinueFlags.Next; } - ContinuationFlags continuationFlags = nextUserContinuation.Flags; + ContinuationFlags continuationFlags = contWithContinueFlags.Flags; const ContinuationFlags continueOnContextFlags = ContinuationFlags.ContinueOnCapturedSynchronizationContext | ContinuationFlags.ContinueOnCapturedTaskScheduler; @@ -564,7 +629,7 @@ internal unsafe bool HandleSuspended(ref RuntimeAsyncAwaitState state) } // Clear continuation flags, so that continuation runs transparently - nextUserContinuation.Flags &= ~continueFlags; + contWithContinueFlags.Flags &= ~continueFlags; valueTaskSourceCont.OnCompletedValueTaskSource( source, @@ -576,6 +641,7 @@ internal unsafe bool HandleSuspended(ref RuntimeAsyncAwaitState state) } else { + Debug.Assert((headContinuation.Flags & continueFlags) == 0); Debug.Assert(stackState->Notifier != null); stackState->Notifier!.OnCompleted(GetContinuationAction()); } @@ -1117,7 +1183,7 @@ private static void RestoreContextsOnSuspension(bool resumed, ExecutionContext? } } - private static void CaptureContinuationContext(ref object continuationContext, ref ContinuationFlags flags) + private static void CaptureContinuationContext(ref object? continuationContext, ref ContinuationFlags flags) { SynchronizationContext? syncCtx = Thread.CurrentThreadAssumedInitialized._synchronizationContext; if (syncCtx != null && syncCtx.GetType() != typeof(SynchronizationContext)) diff --git a/src/coreclr/System.Private.CoreLib/src/System/Runtime/CompilerServices/AsyncHelpers.ValueTaskContinuation.cs b/src/coreclr/System.Private.CoreLib/src/System/Runtime/CompilerServices/AsyncHelpers.ValueTaskContinuation.cs index d7b205787da59e..d9e6e0732c8de6 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/Runtime/CompilerServices/AsyncHelpers.ValueTaskContinuation.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/Runtime/CompilerServices/AsyncHelpers.ValueTaskContinuation.cs @@ -105,6 +105,14 @@ private static class ValueTaskContinuationResume { var vtsCont = (ValueTaskContinuation)cont; vtsCont.Next = null; + + const ContinuationFlags continueFlags = + ContinuationFlags.ContinueOnCapturedSynchronizationContext | + ContinuationFlags.ContinueOnThreadPool | + ContinuationFlags.ContinueOnCapturedTaskScheduler; + + vtsCont.Flags &= ~continueFlags; + t_runtimeAsyncAwaitState.CachedValueTaskContinuation = vtsCont; vtsCont.GetResult(ref result); diff --git a/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/AsyncHelpers.cs b/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/AsyncHelpers.cs index b23e0df9f2f3ac..0311209947e64b 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/AsyncHelpers.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/AsyncHelpers.cs @@ -100,20 +100,25 @@ public static void Await(Task task) /// The value task to await. [Intrinsic] [BypassReadyToRun] - [MethodImpl(MethodImplOptions.Async)] + [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.Async)] [StackTraceHidden] public static T Await(ValueTask task) { - if (task.IsCompleted) + if (!task.IsCompleted) { - return task.Result; + if (task._obj is Task t) + { + TailAwait(); + Await(t); + } + else + { + TailAwait(); + AwaitValueTaskSourceOfT(task._obj!, task._token); + } } - TailAwait(); - TransparentAwaitValueTaskOfT(task); - - // unreachable - while (true) ; + return task.Result; } /// @@ -122,18 +127,25 @@ public static T Await(ValueTask task) /// The task to await. [Intrinsic] [BypassReadyToRun] - [MethodImpl(MethodImplOptions.Async)] + [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.Async)] [StackTraceHidden] public static void Await(ValueTask task) { - if (task.IsCompleted) + if (!task.IsCompleted) { - task.ThrowIfCompletedUnsuccessfully(); - return; + if (task._obj is Task t) + { + TailAwait(); + Await(t); + } + else + { + TailAwait(); + AwaitValueTaskSource(task._obj!, task._token); + } } - TailAwait(); - TransparentAwaitValueTask(task); + task.ThrowIfCompletedUnsuccessfully(); } /// From d50337b36ca2786b12e7db6b36694e61376f7a13 Mon Sep 17 00:00:00 2001 From: vsadov <8218165+VSadov@users.noreply.github.com> Date: Wed, 20 May 2026 05:16:52 -0700 Subject: [PATCH 3/7] PR feedback --- .../System/Runtime/CompilerServices/AsyncHelpers.CoreCLR.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/coreclr/System.Private.CoreLib/src/System/Runtime/CompilerServices/AsyncHelpers.CoreCLR.cs b/src/coreclr/System.Private.CoreLib/src/System/Runtime/CompilerServices/AsyncHelpers.CoreCLR.cs index 038989e90bc4ba..1586c54c96f4e1 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/Runtime/CompilerServices/AsyncHelpers.CoreCLR.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/Runtime/CompilerServices/AsyncHelpers.CoreCLR.cs @@ -380,7 +380,7 @@ private static unsafe void TransparentAwaitValueTask(ValueTask valueTask) // same as above, but with continuation context capture. [BypassReadyToRun] - [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.Async)] + [MethodImpl(MethodImplOptions.Async)] private static unsafe void AwaitValueTaskSource(object source, short token) { ref RuntimeAsyncAwaitState state = ref t_runtimeAsyncAwaitState; @@ -398,6 +398,7 @@ private static unsafe void AwaitValueTaskSource(object source, short token) Debug.Assert(source != null); vtsCont.Initialize(source, token); + vtsCont.ExecutionContext = ExecutionContext.CaptureForSuspension(state.CurrentThread!); // capture continuation context. (this is the difference from the transparent helper) // CONSIDER: We capture ContinuationContext for consistency, but we only need flags. @@ -411,7 +412,7 @@ private static unsafe void AwaitValueTaskSource(object source, short token) } [BypassReadyToRun] - [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.Async)] + [MethodImpl(MethodImplOptions.Async)] private static unsafe void TransparentAwaitValueTaskOfT(ValueTask valueTask) { ref RuntimeAsyncAwaitState state = ref t_runtimeAsyncAwaitState; @@ -456,6 +457,7 @@ private static unsafe void AwaitValueTaskSourceOfT(object source, short token Debug.Assert(source != null); vtsCont.Initialize(source, token); + vtsCont.ExecutionContext = ExecutionContext.CaptureForSuspension(state.CurrentThread!); // CONSIDER: We capture ContinuationContext for consistency, but we only need flags. CaptureContinuationContext(ref vtsCont.ContinuationContext, ref vtsCont.Flags); From 0fb2760db070aa29b72d5fc0d8917355a4f6c77d Mon Sep 17 00:00:00 2001 From: vsadov <8218165+VSadov@users.noreply.github.com> Date: Wed, 20 May 2026 10:38:12 -0700 Subject: [PATCH 4/7] PR feedback. Settle on specializing only VTS --- .../CompilerServices/AsyncHelpers.CoreCLR.cs | 31 ++++++++++++++++--- .../AsyncHelpers.ValueTaskContinuation.cs | 3 +- 2 files changed, 28 insertions(+), 6 deletions(-) diff --git a/src/coreclr/System.Private.CoreLib/src/System/Runtime/CompilerServices/AsyncHelpers.CoreCLR.cs b/src/coreclr/System.Private.CoreLib/src/System/Runtime/CompilerServices/AsyncHelpers.CoreCLR.cs index 1586c54c96f4e1..48ee329a4debfd 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/Runtime/CompilerServices/AsyncHelpers.CoreCLR.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/Runtime/CompilerServices/AsyncHelpers.CoreCLR.cs @@ -400,9 +400,9 @@ private static unsafe void AwaitValueTaskSource(object source, short token) vtsCont.Initialize(source, token); vtsCont.ExecutionContext = ExecutionContext.CaptureForSuspension(state.CurrentThread!); - // capture continuation context. (this is the difference from the transparent helper) - // CONSIDER: We capture ContinuationContext for consistency, but we only need flags. - CaptureContinuationContext(ref vtsCont.ContinuationContext, ref vtsCont.Flags); + // We only need to capture flags. + // When needed, VTS will take the scheduling context captured in the "state". + CaptureContinuationContextFlags(ref vtsCont.Flags); sentinelContinuation.Next = vtsCont; state.StackState->ValueTaskContinuation = vtsCont; @@ -459,8 +459,9 @@ private static unsafe void AwaitValueTaskSourceOfT(object source, short token vtsCont.Initialize(source, token); vtsCont.ExecutionContext = ExecutionContext.CaptureForSuspension(state.CurrentThread!); - // CONSIDER: We capture ContinuationContext for consistency, but we only need flags. - CaptureContinuationContext(ref vtsCont.ContinuationContext, ref vtsCont.Flags); + // We only need to capture flags. + // When needed, VTS will take the scheduling context captured in the "state". + CaptureContinuationContextFlags(ref vtsCont.Flags); sentinelContinuation.Next = vtsCont; state.StackState->ValueTaskContinuation = vtsCont; @@ -1206,6 +1207,26 @@ private static void CaptureContinuationContext(ref object? continuationContext, flags |= ContinuationFlags.ContinueOnThreadPool; } + // Same as above, but only captures flags + private static void CaptureContinuationContextFlags(ref ContinuationFlags flags) + { + SynchronizationContext? syncCtx = Thread.CurrentThreadAssumedInitialized._synchronizationContext; + if (syncCtx != null && syncCtx.GetType() != typeof(SynchronizationContext)) + { + flags |= ContinuationFlags.ContinueOnCapturedSynchronizationContext; + return; + } + + TaskScheduler? sched = TaskScheduler.InternalCurrent; + if (sched != null && sched != TaskScheduler.Default) + { + flags |= ContinuationFlags.ContinueOnCapturedTaskScheduler; + return; + } + + flags |= ContinuationFlags.ContinueOnThreadPool; + } + // Finish suspension in the common case of a custom await or for a ConfigureAwait(false) task await: // - Capture current ExecutionContext into the continuation // - Restore ExecutionContext and SynchronizationContext to the current Thread object diff --git a/src/coreclr/System.Private.CoreLib/src/System/Runtime/CompilerServices/AsyncHelpers.ValueTaskContinuation.cs b/src/coreclr/System.Private.CoreLib/src/System/Runtime/CompilerServices/AsyncHelpers.ValueTaskContinuation.cs index d9e6e0732c8de6..46800f740dcede 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/Runtime/CompilerServices/AsyncHelpers.ValueTaskContinuation.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/Runtime/CompilerServices/AsyncHelpers.ValueTaskContinuation.cs @@ -105,13 +105,14 @@ private static class ValueTaskContinuationResume { var vtsCont = (ValueTaskContinuation)cont; vtsCont.Next = null; + vtsCont.ContinuationContext = null; const ContinuationFlags continueFlags = ContinuationFlags.ContinueOnCapturedSynchronizationContext | ContinuationFlags.ContinueOnThreadPool | ContinuationFlags.ContinueOnCapturedTaskScheduler; - vtsCont.Flags &= ~continueFlags; + Debug.Assert((vtsCont.Flags & continueFlags) == 0); t_runtimeAsyncAwaitState.CachedValueTaskContinuation = vtsCont; From 92f27216aa518cce8ad0fc73931038be88358f5a Mon Sep 17 00:00:00 2001 From: vsadov <8218165+VSadov@users.noreply.github.com> Date: Wed, 20 May 2026 10:53:27 -0700 Subject: [PATCH 5/7] removed NoInlining in right places --- .../System/Runtime/CompilerServices/AsyncHelpers.CoreCLR.cs | 4 ++-- .../src/System/Runtime/CompilerServices/AsyncHelpers.cs | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/coreclr/System.Private.CoreLib/src/System/Runtime/CompilerServices/AsyncHelpers.CoreCLR.cs b/src/coreclr/System.Private.CoreLib/src/System/Runtime/CompilerServices/AsyncHelpers.CoreCLR.cs index 48ee329a4debfd..a9c8822e9ddc37 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/Runtime/CompilerServices/AsyncHelpers.CoreCLR.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/Runtime/CompilerServices/AsyncHelpers.CoreCLR.cs @@ -380,7 +380,7 @@ private static unsafe void TransparentAwaitValueTask(ValueTask valueTask) // same as above, but with continuation context capture. [BypassReadyToRun] - [MethodImpl(MethodImplOptions.Async)] + [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.Async)] private static unsafe void AwaitValueTaskSource(object source, short token) { ref RuntimeAsyncAwaitState state = ref t_runtimeAsyncAwaitState; @@ -412,7 +412,7 @@ private static unsafe void AwaitValueTaskSource(object source, short token) } [BypassReadyToRun] - [MethodImpl(MethodImplOptions.Async)] + [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.Async)] private static unsafe void TransparentAwaitValueTaskOfT(ValueTask valueTask) { ref RuntimeAsyncAwaitState state = ref t_runtimeAsyncAwaitState; diff --git a/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/AsyncHelpers.cs b/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/AsyncHelpers.cs index 0311209947e64b..f54f58afab7e30 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/AsyncHelpers.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/AsyncHelpers.cs @@ -100,7 +100,7 @@ public static void Await(Task task) /// The value task to await. [Intrinsic] [BypassReadyToRun] - [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.Async)] + [MethodImpl(MethodImplOptions.Async)] [StackTraceHidden] public static T Await(ValueTask task) { @@ -127,7 +127,7 @@ public static T Await(ValueTask task) /// The task to await. [Intrinsic] [BypassReadyToRun] - [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.Async)] + [MethodImpl(MethodImplOptions.Async)] [StackTraceHidden] public static void Await(ValueTask task) { From 421a283b315db4c217da56b5bd1261664c72fbbb Mon Sep 17 00:00:00 2001 From: vsadov <8218165+VSadov@users.noreply.github.com> Date: Wed, 20 May 2026 15:50:05 -0700 Subject: [PATCH 6/7] fixes after rebasing, PR feedback --- .../CompilerServices/AsyncHelpers.CoreCLR.cs | 21 +++++++++---------- .../AsyncHelpers.ValueTaskContinuation.cs | 1 - 2 files changed, 10 insertions(+), 12 deletions(-) diff --git a/src/coreclr/System.Private.CoreLib/src/System/Runtime/CompilerServices/AsyncHelpers.CoreCLR.cs b/src/coreclr/System.Private.CoreLib/src/System/Runtime/CompilerServices/AsyncHelpers.CoreCLR.cs index a9c8822e9ddc37..711bddf59abe11 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/Runtime/CompilerServices/AsyncHelpers.CoreCLR.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/Runtime/CompilerServices/AsyncHelpers.CoreCLR.cs @@ -398,11 +398,10 @@ private static unsafe void AwaitValueTaskSource(object source, short token) Debug.Assert(source != null); vtsCont.Initialize(source, token); - vtsCont.ExecutionContext = ExecutionContext.CaptureForSuspension(state.CurrentThread!); // We only need to capture flags. - // When needed, VTS will take the scheduling context captured in the "state". - CaptureContinuationContextFlags(ref vtsCont.Flags); + // If needed, VTS will use the scheduling context captured in the "state". + CaptureContinuationContextFlags(ref vtsCont.Flags, state.CurrentThread!); sentinelContinuation.Next = vtsCont; state.StackState->ValueTaskContinuation = vtsCont; @@ -457,11 +456,10 @@ private static unsafe void AwaitValueTaskSourceOfT(object source, short token Debug.Assert(source != null); vtsCont.Initialize(source, token); - vtsCont.ExecutionContext = ExecutionContext.CaptureForSuspension(state.CurrentThread!); // We only need to capture flags. - // When needed, VTS will take the scheduling context captured in the "state". - CaptureContinuationContextFlags(ref vtsCont.Flags); + // If needed, VTS will use the scheduling context captured in the "state". + CaptureContinuationContextFlags(ref vtsCont.Flags, state.CurrentThread!); sentinelContinuation.Next = vtsCont; state.StackState->ValueTaskContinuation = vtsCont; @@ -559,7 +557,7 @@ internal unsafe bool HandleSuspended(ref RuntimeAsyncAwaitState state) // Head continuation should be the result of async call to AwaitAwaiter or UnsafeAwaitAwaiter. // These never have special continuation context handling. // Except for the scenario with ValueTaskContinuation that wraps ValueTaskSource - // which can capture continuation context. + // which can capture continuation context flags. const ContinuationFlags continueFlags = ContinuationFlags.ContinueOnCapturedSynchronizationContext | ContinuationFlags.ContinueOnThreadPool | @@ -611,8 +609,9 @@ internal unsafe bool HandleSuspended(ref RuntimeAsyncAwaitState state) // Skip to a nontransparent/user continuation. Such continuation must exist. // Since we see a VTS notifier, something was directly or indirectly - // awaiting an async thunk for a ValueTask-returning method. - // That can only happen in nontransparent/user code. + // awaiting either an async thunk for a ValueTask-returning method or + // the direct AsyncHelpers.Await(ValueTask/ValueTask) path. + // In either case, that can only happen in nontransparent/user code. Continuation contWithContinueFlags = valueTaskSourceCont; while ((contWithContinueFlags.Flags & continueFlags) == 0 && contWithContinueFlags.Next != null) { @@ -1208,9 +1207,9 @@ private static void CaptureContinuationContext(ref object? continuationContext, } // Same as above, but only captures flags - private static void CaptureContinuationContextFlags(ref ContinuationFlags flags) + private static void CaptureContinuationContextFlags(ref ContinuationFlags flags, Thread currentThread) { - SynchronizationContext? syncCtx = Thread.CurrentThreadAssumedInitialized._synchronizationContext; + SynchronizationContext? syncCtx = currentThread._synchronizationContext; if (syncCtx != null && syncCtx.GetType() != typeof(SynchronizationContext)) { flags |= ContinuationFlags.ContinueOnCapturedSynchronizationContext; diff --git a/src/coreclr/System.Private.CoreLib/src/System/Runtime/CompilerServices/AsyncHelpers.ValueTaskContinuation.cs b/src/coreclr/System.Private.CoreLib/src/System/Runtime/CompilerServices/AsyncHelpers.ValueTaskContinuation.cs index 46800f740dcede..7d5af875d08a50 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/Runtime/CompilerServices/AsyncHelpers.ValueTaskContinuation.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/Runtime/CompilerServices/AsyncHelpers.ValueTaskContinuation.cs @@ -105,7 +105,6 @@ private static class ValueTaskContinuationResume { var vtsCont = (ValueTaskContinuation)cont; vtsCont.Next = null; - vtsCont.ContinuationContext = null; const ContinuationFlags continueFlags = ContinuationFlags.ContinueOnCapturedSynchronizationContext | From 33be9338c2b23899d8e846ebc15d4e2f8a3eb24d Mon Sep 17 00:00:00 2001 From: vsadov <8218165+VSadov@users.noreply.github.com> Date: Wed, 20 May 2026 15:53:22 -0700 Subject: [PATCH 7/7] removed a now confusing comment --- .../src/System/Runtime/CompilerServices/AsyncHelpers.CoreCLR.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/coreclr/System.Private.CoreLib/src/System/Runtime/CompilerServices/AsyncHelpers.CoreCLR.cs b/src/coreclr/System.Private.CoreLib/src/System/Runtime/CompilerServices/AsyncHelpers.CoreCLR.cs index 711bddf59abe11..bf742b7a505f66 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/Runtime/CompilerServices/AsyncHelpers.CoreCLR.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/Runtime/CompilerServices/AsyncHelpers.CoreCLR.cs @@ -378,7 +378,6 @@ private static unsafe void TransparentAwaitValueTask(ValueTask valueTask) AsyncSuspend(vtsCont); } - // same as above, but with continuation context capture. [BypassReadyToRun] [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.Async)] private static unsafe void AwaitValueTaskSource(object source, short token)