From e72db90e4b04238e56d843e1550a8d59fdcbfc05 Mon Sep 17 00:00:00 2001 From: Jakob Botsch Nielsen Date: Mon, 8 Dec 2025 12:54:51 +0100 Subject: [PATCH 1/2] Use `VoidTaskResult` for void returns in runtime async Removes some abstraction and unifies behavior with async 1, at the cost of slightly larger instances for void methods. --- .../CompilerServices/AsyncHelpers.CoreCLR.cs | 279 +++++------------- 1 file changed, 79 insertions(+), 200 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 0045c9e53dbf3d..725ae8ab961fb4 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 @@ -267,17 +267,6 @@ private static void TransparentAwait(object o) AsyncSuspend(sentinelContinuation); } - private interface IRuntimeAsyncTaskOps - { - static abstract Action GetContinuationAction(T task); - static abstract Continuation MoveContinuationState(T task); - static abstract void SetContinuationState(T task, Continuation value); - static abstract bool SetCompleted(T task); - static abstract void PostToSyncContext(T task, SynchronizationContext syncCtx); - static abstract void ValueTaskSourceOnCompleted(T task, IValueTaskSourceNotifier vtsNotifier, ValueTaskSourceOnCompletedFlags configFlags); - static abstract ref byte GetResultStorage(T task); - } - // Represents execution of a chain of suspended and resuming runtime // async functions. private sealed class RuntimeAsyncTask : Task, ITaskCompletionAction @@ -288,195 +277,48 @@ public RuntimeAsyncTask() // Ensure that state object isn't published out for others to see. Debug.Assert((m_stateFlags & (int)InternalTaskOptions.PromiseTask) != 0, "Expected state flags to already be configured."); Debug.Assert(m_stateObject is null, "Expected to be able to use the state object field for Continuation."); - m_action = MoveNext; + m_action = DispatchContinuations; m_stateFlags |= (int)InternalTaskOptions.HiddenState; } internal override void ExecuteFromThreadPool(Thread threadPoolThread) { - MoveNext(); - } - - private void MoveNext() - { - RuntimeAsyncTaskCore.DispatchContinuations, Ops>(this); - } - - public void HandleSuspended() - { - RuntimeAsyncTaskCore.HandleSuspended, Ops>(this); + DispatchContinuations(); } void ITaskCompletionAction.Invoke(Task completingTask) { - MoveNext(); + DispatchContinuations(); } bool ITaskCompletionAction.InvokeMayRunArbitraryCode => true; - private static readonly SendOrPostCallback s_postCallback = static state => - { - Debug.Assert(state is RuntimeAsyncTask); - ((RuntimeAsyncTask)state).MoveNext(); - }; - - public static readonly Action s_runContinuationAction = static state => - { - Debug.Assert(state is RuntimeAsyncTask); - ((RuntimeAsyncTask)state).MoveNext(); - }; + private Action GetContinuationAction() => (Action)m_action!; - private struct Ops : IRuntimeAsyncTaskOps> + public Continuation MoveContinuationState() { - public static Action GetContinuationAction(RuntimeAsyncTask task) => (Action)task.m_action!; - public static Continuation MoveContinuationState(RuntimeAsyncTask task) - { - Continuation continuation = (Continuation)task.m_stateObject!; - task.m_stateObject = null; - return continuation; - } - - public static void SetContinuationState(RuntimeAsyncTask task, Continuation value) - { - Debug.Assert(task.m_stateObject == null); - task.m_stateObject = value; - } - - public static bool SetCompleted(RuntimeAsyncTask task) - { - return task.TrySetResult(task.m_result); - } - - public static void PostToSyncContext(RuntimeAsyncTask task, SynchronizationContext syncContext) - { - syncContext.Post(s_postCallback, task); - } - - public static void ValueTaskSourceOnCompleted(RuntimeAsyncTask task, IValueTaskSourceNotifier vtsNotifier, ValueTaskSourceOnCompletedFlags configFlags) - { - vtsNotifier.OnCompleted(s_runContinuationAction, task, configFlags); - } - - public static ref byte GetResultStorage(RuntimeAsyncTask task) => ref Unsafe.As(ref task.m_result); + Continuation continuation = (Continuation)m_stateObject!; + m_stateObject = null; + return continuation; } - } - // Represents execution of a chain of suspended and resuming runtime - // async functions. - private sealed class RuntimeAsyncTask : Task, ITaskCompletionAction - { - public RuntimeAsyncTask() - { - // We use the base Task's state object field to store the Continuation while posting the task around. - // Ensure that state object isn't published out for others to see. - Debug.Assert((m_stateFlags & (int)InternalTaskOptions.PromiseTask) != 0, "Expected state flags to already be configured."); - Debug.Assert(m_stateObject is null, "Expected to be able to use the state object field for Continuation."); - m_action = MoveNext; - m_stateFlags |= (int)InternalTaskOptions.HiddenState; - } - - internal override void ExecuteFromThreadPool(Thread threadPoolThread) + public void SetContinuationState(Continuation value) { - MoveNext(); + Debug.Assert(m_stateObject == null); + m_stateObject = value; } - private void MoveNext() - { - RuntimeAsyncTaskCore.DispatchContinuations(this); - } + public ref byte GetResultStorage() => ref Unsafe.As(ref m_result); - public void HandleSuspended() - { - RuntimeAsyncTaskCore.HandleSuspended(this); - } - - void ITaskCompletionAction.Invoke(Task completingTask) - { - MoveNext(); - } - - bool ITaskCompletionAction.InvokeMayRunArbitraryCode => true; - - private static readonly SendOrPostCallback s_postCallback = static state => - { - Debug.Assert(state is RuntimeAsyncTask); - ((RuntimeAsyncTask)state).MoveNext(); - }; - - public static readonly Action s_runContinuationAction = static state => - { - Debug.Assert(state is RuntimeAsyncTask); - ((RuntimeAsyncTask)state).MoveNext(); - }; - - private struct Ops : IRuntimeAsyncTaskOps - { - public static Action GetContinuationAction(RuntimeAsyncTask task) => (Action)task.m_action!; - public static Continuation MoveContinuationState(RuntimeAsyncTask task) - { - Continuation continuation = (Continuation)task.m_stateObject!; - task.m_stateObject = null; - return continuation; - } - - public static void SetContinuationState(RuntimeAsyncTask task, Continuation value) - { - Debug.Assert(task.m_stateObject == null); - task.m_stateObject = value; - } - - public static bool SetCompleted(RuntimeAsyncTask task) - { - return task.TrySetResult(); - } - - public static void PostToSyncContext(RuntimeAsyncTask task, SynchronizationContext syncContext) - { - syncContext.Post(s_postCallback, task); - } - - public static void ValueTaskSourceOnCompleted(RuntimeAsyncTask task, IValueTaskSourceNotifier vtsNotifier, ValueTaskSourceOnCompletedFlags configFlags) - { - vtsNotifier.OnCompleted(s_runContinuationAction, task, configFlags); - } - - public static ref byte GetResultStorage(RuntimeAsyncTask task) => ref Unsafe.NullRef(); - } - } - - private static class RuntimeAsyncTaskCore - { - [StructLayout(LayoutKind.Explicit)] - private unsafe ref struct DispatcherInfo - { - // Dispatcher info for next dispatcher present on stack, or - // null if none. - [FieldOffset(0)] - public DispatcherInfo* Next; - - // Next continuation the dispatcher will process. -#if TARGET_64BIT - [FieldOffset(8)] -#else - [FieldOffset(4)] -#endif - public Continuation? NextContinuation; - } - - // Information about current task dispatching, to be used for async - // stackwalking. - [ThreadStatic] - private static unsafe DispatcherInfo* t_dispatcherInfo; - - public static unsafe void DispatchContinuations(T task) where T : Task, ITaskCompletionAction where TOps : IRuntimeAsyncTaskOps + private unsafe void DispatchContinuations() { ExecutionAndSyncBlockStore contexts = default; contexts.Push(); - DispatcherInfo dispatcherInfo; - dispatcherInfo.Next = t_dispatcherInfo; - dispatcherInfo.NextContinuation = TOps.MoveContinuationState(task); - t_dispatcherInfo = &dispatcherInfo; + RuntimeAsyncTaskCore.DispatcherInfo dispatcherInfo; + dispatcherInfo.Next = RuntimeAsyncTaskCore.t_dispatcherInfo; + dispatcherInfo.NextContinuation = MoveContinuationState(); + RuntimeAsyncTaskCore.t_dispatcherInfo = &dispatcherInfo; while (true) { @@ -487,15 +329,15 @@ public static unsafe void DispatchContinuations(T task) where T : Task, Continuation? nextContinuation = curContinuation.Next; dispatcherInfo.NextContinuation = nextContinuation; - ref byte resultLoc = ref nextContinuation != null ? ref nextContinuation.GetResultStorageOrNull() : ref TOps.GetResultStorage(task); + ref byte resultLoc = ref nextContinuation != null ? ref nextContinuation.GetResultStorageOrNull() : ref GetResultStorage(); Continuation? newContinuation = curContinuation.ResumeInfo->Resume(curContinuation, ref resultLoc); if (newContinuation != null) { newContinuation.Next = nextContinuation; - HandleSuspended(task); + HandleSuspended(); contexts.Pop(); - t_dispatcherInfo = dispatcherInfo.Next; + RuntimeAsyncTaskCore.t_dispatcherInfo = dispatcherInfo.Next; return; } } @@ -506,12 +348,12 @@ public static unsafe void DispatchContinuations(T task) where T : Task, { // Tail of AsyncTaskMethodBuilderT.SetException bool successfullySet = ex is OperationCanceledException oce ? - task.TrySetCanceled(oce.CancellationToken, oce) : - task.TrySetException(ex); + TrySetCanceled(oce.CancellationToken, oce) : + TrySetException(ex); contexts.Pop(); - t_dispatcherInfo = dispatcherInfo.Next; + RuntimeAsyncTaskCore.t_dispatcherInfo = dispatcherInfo.Next; if (!successfullySet) { @@ -527,11 +369,11 @@ public static unsafe void DispatchContinuations(T task) where T : Task, if (dispatcherInfo.NextContinuation == null) { - bool successfullySet = TOps.SetCompleted(task); + bool successfullySet = TrySetResult(m_result); contexts.Pop(); - t_dispatcherInfo = dispatcherInfo.Next; + RuntimeAsyncTaskCore.t_dispatcherInfo = dispatcherInfo.Next; if (!successfullySet) { @@ -541,10 +383,10 @@ public static unsafe void DispatchContinuations(T task) where T : Task, return; } - if (QueueContinuationFollowUpActionIfNecessary(task, dispatcherInfo.NextContinuation)) + if (QueueContinuationFollowUpActionIfNecessary(dispatcherInfo.NextContinuation)) { contexts.Pop(); - t_dispatcherInfo = dispatcherInfo.Next; + RuntimeAsyncTaskCore.t_dispatcherInfo = dispatcherInfo.Next; return; } } @@ -561,7 +403,7 @@ public static unsafe void DispatchContinuations(T task) where T : Task, } } - public static void HandleSuspended(T task) where T : Task, ITaskCompletionAction where TOps : IRuntimeAsyncTaskOps + public void HandleSuspended() { ref RuntimeAsyncAwaitState state = ref t_runtimeAsyncAwaitState; @@ -592,22 +434,22 @@ public static void HandleSuspended(T task) where T : Task, ITaskComplet Debug.Assert((headContinuation.Flags & continueFlags) == 0); - TOps.SetContinuationState(task, headContinuation); + SetContinuationState(headContinuation); try { if (critNotifier != null) { - critNotifier.UnsafeOnCompleted(TOps.GetContinuationAction(task)); + critNotifier.UnsafeOnCompleted(GetContinuationAction()); } else if (taskNotifier != null) { // Runtime async callable wrapper for task returning // method. This implements the context transparent // forwarding and makes these wrappers minimal cost. - if (!taskNotifier.TryAddCompletionAction(task)) + if (!taskNotifier.TryAddCompletionAction(this)) { - ThreadPool.UnsafeQueueUserWorkItemInternal(task, preferLocal: true); + ThreadPool.UnsafeQueueUserWorkItemInternal(this, preferLocal: true); } } else if (vtsNotifier != null) @@ -645,12 +487,12 @@ public static void HandleSuspended(T task) where T : Task, ITaskComplet // Clear continuation flags, so that continuation runs transparently nextUserContinuation.Flags &= ~continueFlags; - TOps.ValueTaskSourceOnCompleted(task, vtsNotifier, configFlags); + vtsNotifier.OnCompleted(s_runContinuationAction, this, configFlags); } else { Debug.Assert(notifier != null); - notifier.OnCompleted(TOps.GetContinuationAction(task)); + notifier.OnCompleted(GetContinuationAction()); } } catch (Exception ex) @@ -659,7 +501,7 @@ public static void HandleSuspended(T task) where T : Task, ITaskComplet } } - private static bool QueueContinuationFollowUpActionIfNecessary(T task, Continuation continuation) where T : Task where TOps : IRuntimeAsyncTaskOps + private bool QueueContinuationFollowUpActionIfNecessary(Continuation continuation) { if ((continuation.Flags & ContinuationFlags.ContinueOnThreadPool) != 0) { @@ -674,8 +516,8 @@ private static bool QueueContinuationFollowUpActionIfNecessary(T task, } } - TOps.SetContinuationState(task, continuation); - ThreadPool.UnsafeQueueUserWorkItemInternal(task, preferLocal: true); + SetContinuationState(continuation); + ThreadPool.UnsafeQueueUserWorkItemInternal(this, preferLocal: true); return true; } @@ -691,11 +533,11 @@ private static bool QueueContinuationFollowUpActionIfNecessary(T task, return false; } - TOps.SetContinuationState(task, continuation); + SetContinuationState(continuation); try { - TOps.PostToSyncContext(task, continuationSyncCtx); + continuationSyncCtx.Post(s_postCallback, this); } catch (Exception ex) { @@ -711,9 +553,9 @@ private static bool QueueContinuationFollowUpActionIfNecessary(T task, Debug.Assert(continuationContext is TaskScheduler { }); TaskScheduler sched = (TaskScheduler)continuationContext; - TOps.SetContinuationState(task, continuation); + SetContinuationState(continuation); // TODO: We do not need TaskSchedulerAwaitTaskContinuation here, just need to refactor its Run method... - var taskSchedCont = new TaskSchedulerAwaitTaskContinuation(sched, TOps.GetContinuationAction(task), flowExecutionContext: false); + var taskSchedCont = new TaskSchedulerAwaitTaskContinuation(sched, GetContinuationAction(), flowExecutionContext: false); taskSchedCont.Run(Task.CompletedTask, canInlineContinuationTask: true); return true; @@ -721,6 +563,43 @@ private static bool QueueContinuationFollowUpActionIfNecessary(T task, return false; } + + private static readonly SendOrPostCallback s_postCallback = static state => + { + Debug.Assert(state is RuntimeAsyncTask); + ((RuntimeAsyncTask)state).DispatchContinuations(); + }; + + public static readonly Action s_runContinuationAction = static state => + { + Debug.Assert(state is RuntimeAsyncTask); + ((RuntimeAsyncTask)state).DispatchContinuations(); + }; + } + + internal static class RuntimeAsyncTaskCore + { + [StructLayout(LayoutKind.Explicit)] + internal unsafe ref struct DispatcherInfo + { + // Dispatcher info for next dispatcher present on stack, or + // null if none. + [FieldOffset(0)] + public DispatcherInfo* Next; + + // Next continuation the dispatcher will process. +#if TARGET_64BIT + [FieldOffset(8)] +#else + [FieldOffset(4)] +#endif + public Continuation? NextContinuation; + } + + // Information about current task dispatching, to be used for async + // stackwalking. + [ThreadStatic] + internal static unsafe DispatcherInfo* t_dispatcherInfo; } // Change return type to RuntimeAsyncTask -- no benefit since this is used for Task returning thunks only @@ -736,7 +615,7 @@ private static bool QueueContinuationFollowUpActionIfNecessary(T task, private static Task FinalizeTaskReturningThunk() { - RuntimeAsyncTask result = new(); + RuntimeAsyncTask result = new(); result.HandleSuspended(); return result; } From 3825e80c4c6e56797ea63324755bd338267f1b63 Mon Sep 17 00:00:00 2001 From: Jakob Botsch Nielsen Date: Mon, 8 Dec 2025 13:09:23 +0100 Subject: [PATCH 2/2] Some nits --- .../CompilerServices/AsyncHelpers.CoreCLR.cs | 198 +++++++++--------- 1 file changed, 99 insertions(+), 99 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 725ae8ab961fb4..b429a94e5160d3 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 @@ -295,115 +295,20 @@ void ITaskCompletionAction.Invoke(Task completingTask) private Action GetContinuationAction() => (Action)m_action!; - public Continuation MoveContinuationState() + private Continuation MoveContinuationState() { Continuation continuation = (Continuation)m_stateObject!; m_stateObject = null; return continuation; } - public void SetContinuationState(Continuation value) + private void SetContinuationState(Continuation value) { Debug.Assert(m_stateObject == null); m_stateObject = value; } - public ref byte GetResultStorage() => ref Unsafe.As(ref m_result); - - private unsafe void DispatchContinuations() - { - ExecutionAndSyncBlockStore contexts = default; - contexts.Push(); - - RuntimeAsyncTaskCore.DispatcherInfo dispatcherInfo; - dispatcherInfo.Next = RuntimeAsyncTaskCore.t_dispatcherInfo; - dispatcherInfo.NextContinuation = MoveContinuationState(); - RuntimeAsyncTaskCore.t_dispatcherInfo = &dispatcherInfo; - - while (true) - { - Debug.Assert(dispatcherInfo.NextContinuation != null); - try - { - Continuation curContinuation = dispatcherInfo.NextContinuation; - Continuation? nextContinuation = curContinuation.Next; - dispatcherInfo.NextContinuation = nextContinuation; - - ref byte resultLoc = ref nextContinuation != null ? ref nextContinuation.GetResultStorageOrNull() : ref GetResultStorage(); - Continuation? newContinuation = curContinuation.ResumeInfo->Resume(curContinuation, ref resultLoc); - - if (newContinuation != null) - { - newContinuation.Next = nextContinuation; - HandleSuspended(); - contexts.Pop(); - RuntimeAsyncTaskCore.t_dispatcherInfo = dispatcherInfo.Next; - return; - } - } - catch (Exception ex) - { - Continuation? handlerContinuation = UnwindToPossibleHandler(dispatcherInfo.NextContinuation); - if (handlerContinuation == null) - { - // Tail of AsyncTaskMethodBuilderT.SetException - bool successfullySet = ex is OperationCanceledException oce ? - TrySetCanceled(oce.CancellationToken, oce) : - TrySetException(ex); - - contexts.Pop(); - - RuntimeAsyncTaskCore.t_dispatcherInfo = dispatcherInfo.Next; - - if (!successfullySet) - { - ThrowHelper.ThrowInvalidOperationException(ExceptionResource.TaskT_TransitionToFinal_AlreadyCompleted); - } - - return; - } - - handlerContinuation.SetException(ex); - dispatcherInfo.NextContinuation = handlerContinuation; - } - - if (dispatcherInfo.NextContinuation == null) - { - bool successfullySet = TrySetResult(m_result); - - contexts.Pop(); - - RuntimeAsyncTaskCore.t_dispatcherInfo = dispatcherInfo.Next; - - if (!successfullySet) - { - ThrowHelper.ThrowInvalidOperationException(ExceptionResource.TaskT_TransitionToFinal_AlreadyCompleted); - } - - return; - } - - if (QueueContinuationFollowUpActionIfNecessary(dispatcherInfo.NextContinuation)) - { - contexts.Pop(); - RuntimeAsyncTaskCore.t_dispatcherInfo = dispatcherInfo.Next; - return; - } - } - } - - private static Continuation? UnwindToPossibleHandler(Continuation? continuation) - { - while (true) - { - if (continuation == null || (continuation.Flags & ContinuationFlags.HasException) != 0) - return continuation; - - continuation = continuation.Next; - } - } - - public void HandleSuspended() + internal void HandleSuspended() { ref RuntimeAsyncAwaitState state = ref t_runtimeAsyncAwaitState; @@ -501,6 +406,101 @@ public void HandleSuspended() } } + private unsafe void DispatchContinuations() + { + ExecutionAndSyncBlockStore contexts = default; + contexts.Push(); + + RuntimeAsyncTaskCore.DispatcherInfo dispatcherInfo; + dispatcherInfo.Next = RuntimeAsyncTaskCore.t_dispatcherInfo; + dispatcherInfo.NextContinuation = MoveContinuationState(); + RuntimeAsyncTaskCore.t_dispatcherInfo = &dispatcherInfo; + + while (true) + { + Debug.Assert(dispatcherInfo.NextContinuation != null); + try + { + Continuation curContinuation = dispatcherInfo.NextContinuation; + Continuation? nextContinuation = curContinuation.Next; + dispatcherInfo.NextContinuation = nextContinuation; + + ref byte resultLoc = ref nextContinuation != null ? ref nextContinuation.GetResultStorageOrNull() : ref GetResultStorage(); + Continuation? newContinuation = curContinuation.ResumeInfo->Resume(curContinuation, ref resultLoc); + + if (newContinuation != null) + { + newContinuation.Next = nextContinuation; + HandleSuspended(); + contexts.Pop(); + RuntimeAsyncTaskCore.t_dispatcherInfo = dispatcherInfo.Next; + return; + } + } + catch (Exception ex) + { + Continuation? handlerContinuation = UnwindToPossibleHandler(dispatcherInfo.NextContinuation); + if (handlerContinuation == null) + { + // Tail of AsyncTaskMethodBuilderT.SetException + bool successfullySet = ex is OperationCanceledException oce ? + TrySetCanceled(oce.CancellationToken, oce) : + TrySetException(ex); + + contexts.Pop(); + + RuntimeAsyncTaskCore.t_dispatcherInfo = dispatcherInfo.Next; + + if (!successfullySet) + { + ThrowHelper.ThrowInvalidOperationException(ExceptionResource.TaskT_TransitionToFinal_AlreadyCompleted); + } + + return; + } + + handlerContinuation.SetException(ex); + dispatcherInfo.NextContinuation = handlerContinuation; + } + + if (dispatcherInfo.NextContinuation == null) + { + bool successfullySet = TrySetResult(m_result); + + contexts.Pop(); + + RuntimeAsyncTaskCore.t_dispatcherInfo = dispatcherInfo.Next; + + if (!successfullySet) + { + ThrowHelper.ThrowInvalidOperationException(ExceptionResource.TaskT_TransitionToFinal_AlreadyCompleted); + } + + return; + } + + if (QueueContinuationFollowUpActionIfNecessary(dispatcherInfo.NextContinuation)) + { + contexts.Pop(); + RuntimeAsyncTaskCore.t_dispatcherInfo = dispatcherInfo.Next; + return; + } + } + } + + private ref byte GetResultStorage() => ref Unsafe.As(ref m_result); + + private static Continuation? UnwindToPossibleHandler(Continuation? continuation) + { + while (true) + { + if (continuation == null || (continuation.Flags & ContinuationFlags.HasException) != 0) + return continuation; + + continuation = continuation.Next; + } + } + private bool QueueContinuationFollowUpActionIfNecessary(Continuation continuation) { if ((continuation.Flags & ContinuationFlags.ContinueOnThreadPool) != 0) @@ -570,7 +570,7 @@ private bool QueueContinuationFollowUpActionIfNecessary(Continuation continuatio ((RuntimeAsyncTask)state).DispatchContinuations(); }; - public static readonly Action s_runContinuationAction = static state => + private static readonly Action s_runContinuationAction = static state => { Debug.Assert(state is RuntimeAsyncTask); ((RuntimeAsyncTask)state).DispatchContinuations();