From 9f4033413a6950db3eae96b5ee60a438d340a052 Mon Sep 17 00:00:00 2001 From: vsadov <8218165+VSadov@users.noreply.github.com> Date: Tue, 7 Apr 2026 16:38:20 -0700 Subject: [PATCH 1/3] Reuse ValueTaskSourceNotifier instance --- .../CompilerServices/AsyncHelpers.CoreCLR.cs | 6 +- .../src/System/Threading/Tasks/ValueTask.cs | 92 ++++++++++++------- 2 files changed, 61 insertions(+), 37 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 dc0fafd0a0e4cb..d39d92867891d6 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 @@ -217,7 +217,7 @@ private struct RuntimeAsyncAwaitState // to one of these notifiers. public ICriticalNotifyCompletion? CriticalNotifier; public INotifyCompletion? Notifier; - public IValueTaskSourceNotifier? ValueTaskSourceNotifier; + public ValueTaskSourceNotifier? ValueTaskSourceNotifier; public Task? TaskNotifier; public ExecutionContext? ExecutionContext; @@ -310,7 +310,7 @@ private static void TransparentAwait(object o) } else { - state.ValueTaskSourceNotifier = (IValueTaskSourceNotifier)o; + state.ValueTaskSourceNotifier = (ValueTaskSourceNotifier)o; } state.CaptureContexts(); @@ -366,7 +366,7 @@ internal void HandleSuspended() ICriticalNotifyCompletion? critNotifier = state.CriticalNotifier; INotifyCompletion? notifier = state.Notifier; - IValueTaskSourceNotifier? vtsNotifier = state.ValueTaskSourceNotifier; + ValueTaskSourceNotifier? vtsNotifier = state.ValueTaskSourceNotifier; Task? taskNotifier = state.TaskNotifier; state.CriticalNotifier = null; diff --git a/src/libraries/System.Private.CoreLib/src/System/Threading/Tasks/ValueTask.cs b/src/libraries/System.Private.CoreLib/src/System/Threading/Tasks/ValueTask.cs index 692cfbb0a32cc3..e31240b82c50b9 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Threading/Tasks/ValueTask.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Threading/Tasks/ValueTask.cs @@ -179,28 +179,17 @@ obj as Task ?? GetTaskForValueTaskSource(Unsafe.As(obj)); } + private static readonly Action, object?, short, ValueTaskSourceOnCompletedFlags> onCompleted = + (object obj, Action continuation, object? state, short token, ValueTaskSourceOnCompletedFlags flags) => + Unsafe.As(obj).OnCompleted(continuation, state, token, flags); + internal object AsTaskOrNotifier() { object? obj = _obj; Debug.Assert(obj is Task || obj is IValueTaskSource); return obj as Task ?? - (object)new ValueTaskSourceNotifier(Unsafe.As(obj), _token); - } - - private sealed class ValueTaskSourceNotifier : IValueTaskSourceNotifier - { - private IValueTaskSource _valueTaskSource; - private short _token; - - public ValueTaskSourceNotifier(IValueTaskSource valueTaskSource, short token) - { - _valueTaskSource = valueTaskSource; - _token = token; - } - - public void OnCompleted(Action continuation, object? state, ValueTaskSourceOnCompletedFlags flags) => - _valueTaskSource.OnCompleted(continuation, state, _token, flags); + (object)ValueTaskSourceNotifier.GetInstance(obj, onCompleted, _token); } /// Gets a that may be used at any point in the future. @@ -612,28 +601,17 @@ public Task AsTask() return GetTaskForValueTaskSource(Unsafe.As>(obj)); } + private static readonly Action, object?, short, ValueTaskSourceOnCompletedFlags> onCompleted = + (object obj, Action continuation, object? state, short token, ValueTaskSourceOnCompletedFlags flags) => + Unsafe.As>(obj).OnCompleted(continuation, state, token, flags); + internal object AsTaskOrNotifier() { object? obj = _obj; Debug.Assert(obj is Task || obj is IValueTaskSource); return obj as Task ?? - (object)new ValueTaskSourceNotifier(Unsafe.As>(obj), _token); - } - - private sealed class ValueTaskSourceNotifier : IValueTaskSourceNotifier - { - private IValueTaskSource _valueTaskSource; - private short _token; - - public ValueTaskSourceNotifier(IValueTaskSource valueTaskSource, short token) - { - _valueTaskSource = valueTaskSource; - _token = token; - } - - public void OnCompleted(Action continuation, object? state, ValueTaskSourceOnCompletedFlags flags) => - _valueTaskSource.OnCompleted(continuation, state, _token, flags); + (object)ValueTaskSourceNotifier.GetInstance(obj, onCompleted, _token); } /// Gets a that may be used at any point in the future. @@ -897,8 +875,54 @@ public ConfiguredValueTaskAwaitable ConfigureAwait(bool continueOnCaptu } } - internal interface IValueTaskSourceNotifier + internal sealed class ValueTaskSourceNotifier { - void OnCompleted(Action continuation, object? state, ValueTaskSourceOnCompletedFlags flags); + // ValueTaskSourceNotifier is used only during suspension sequence, thus + // a given thread will never need more than one instance. + // We will just reuse the same instance when needed. + [ThreadStatic] + private static ValueTaskSourceNotifier? t_instance; + + private object _source; + private Action, object?, short, ValueTaskSourceOnCompletedFlags> _onCompleted; + private short _token; + + private ValueTaskSourceNotifier( + object source, + Action, object?, short, ValueTaskSourceOnCompletedFlags> onCompleted, + short token) + { + _source = source; + _onCompleted = onCompleted; + _token = token; + } + + public static ValueTaskSourceNotifier GetInstance( + object source, + Action, object?, short, ValueTaskSourceOnCompletedFlags> onCompleted, + short token) + { + ValueTaskSourceNotifier? instance = t_instance; + if (instance == null) + { + return t_instance = new ValueTaskSourceNotifier(source, onCompleted, token); + } + + instance._source = source; + instance._onCompleted = onCompleted; + instance._token = token; + + return instance; + } + + public void OnCompleted(Action continuation, object? state, ValueTaskSourceOnCompletedFlags flags) + { + _onCompleted(_source, continuation, state, _token, flags); + + // The data that we store is effectively single-use. + // Once used, clear the fields to not retain unknown data. + _source = _onCompleted = null!; + _token = 0; + } } } From 1df8ffe12d2ee0d69e383440429ca96b0b409865 Mon Sep 17 00:00:00 2001 From: vsadov <8218165+VSadov@users.noreply.github.com> Date: Tue, 7 Apr 2026 17:59:01 -0700 Subject: [PATCH 2/3] PR feedback --- .../src/System/Threading/Tasks/ValueTask.cs | 24 ++++++++++++------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/src/libraries/System.Private.CoreLib/src/System/Threading/Tasks/ValueTask.cs b/src/libraries/System.Private.CoreLib/src/System/Threading/Tasks/ValueTask.cs index e31240b82c50b9..16b2c5bbb055c0 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Threading/Tasks/ValueTask.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Threading/Tasks/ValueTask.cs @@ -179,9 +179,13 @@ obj as Task ?? GetTaskForValueTaskSource(Unsafe.As(obj)); } - private static readonly Action, object?, short, ValueTaskSourceOnCompletedFlags> onCompleted = - (object obj, Action continuation, object? state, short token, ValueTaskSourceOnCompletedFlags flags) => - Unsafe.As(obj).OnCompleted(continuation, state, token, flags); + // A class to not have a static initializer directly on ValueTask + private static class OnCompleted + { + internal static readonly Action, object?, short, ValueTaskSourceOnCompletedFlags> s_action = + static (object obj, Action continuation, object? state, short token, ValueTaskSourceOnCompletedFlags flags) => + Unsafe.As(obj).OnCompleted(continuation, state, token, flags); + } internal object AsTaskOrNotifier() { @@ -189,7 +193,7 @@ internal object AsTaskOrNotifier() Debug.Assert(obj is Task || obj is IValueTaskSource); return obj as Task ?? - (object)ValueTaskSourceNotifier.GetInstance(obj, onCompleted, _token); + (object)ValueTaskSourceNotifier.GetInstance(obj, OnCompleted.s_action, _token); } /// Gets a that may be used at any point in the future. @@ -601,9 +605,13 @@ public Task AsTask() return GetTaskForValueTaskSource(Unsafe.As>(obj)); } - private static readonly Action, object?, short, ValueTaskSourceOnCompletedFlags> onCompleted = - (object obj, Action continuation, object? state, short token, ValueTaskSourceOnCompletedFlags flags) => - Unsafe.As>(obj).OnCompleted(continuation, state, token, flags); + // A class to not have a static initializer directly on ValueTask + private static class OnCompleted + { + internal static readonly Action, object?, short, ValueTaskSourceOnCompletedFlags> s_action = + static (object obj, Action continuation, object? state, short token, ValueTaskSourceOnCompletedFlags flags) => + Unsafe.As>(obj).OnCompleted(continuation, state, token, flags); + } internal object AsTaskOrNotifier() { @@ -611,7 +619,7 @@ internal object AsTaskOrNotifier() Debug.Assert(obj is Task || obj is IValueTaskSource); return obj as Task ?? - (object)ValueTaskSourceNotifier.GetInstance(obj, onCompleted, _token); + (object)ValueTaskSourceNotifier.GetInstance(obj, OnCompleted.s_action, _token); } /// Gets a that may be used at any point in the future. From b0adb1fcc18c945839b5217332a6f34fa6d3393d Mon Sep 17 00:00:00 2001 From: vsadov <8218165+VSadov@users.noreply.github.com> Date: Wed, 8 Apr 2026 18:52:22 -0700 Subject: [PATCH 3/3] Use function pointers --- .../src/System/Threading/Tasks/ValueTask.cs | 45 +++++++++---------- 1 file changed, 20 insertions(+), 25 deletions(-) diff --git a/src/libraries/System.Private.CoreLib/src/System/Threading/Tasks/ValueTask.cs b/src/libraries/System.Private.CoreLib/src/System/Threading/Tasks/ValueTask.cs index 16b2c5bbb055c0..c20a2e8b7cb7b9 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Threading/Tasks/ValueTask.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Threading/Tasks/ValueTask.cs @@ -179,21 +179,19 @@ obj as Task ?? GetTaskForValueTaskSource(Unsafe.As(obj)); } - // A class to not have a static initializer directly on ValueTask - private static class OnCompleted - { - internal static readonly Action, object?, short, ValueTaskSourceOnCompletedFlags> s_action = - static (object obj, Action continuation, object? state, short token, ValueTaskSourceOnCompletedFlags flags) => - Unsafe.As(obj).OnCompleted(continuation, state, token, flags); - } + /// + /// Helper to invoke IValueTaskSource.OnCompleted from a caller that does not know the actual type of the source. + /// + private static void OnCompleted(object obj, Action continuation, object? state, short token, ValueTaskSourceOnCompletedFlags flags) => + Unsafe.As(obj).OnCompleted(continuation, state, token, flags); - internal object AsTaskOrNotifier() + internal unsafe object AsTaskOrNotifier() { object? obj = _obj; Debug.Assert(obj is Task || obj is IValueTaskSource); return obj as Task ?? - (object)ValueTaskSourceNotifier.GetInstance(obj, OnCompleted.s_action, _token); + (object)ValueTaskSourceNotifier.GetInstance(obj, &OnCompleted, _token); } /// Gets a that may be used at any point in the future. @@ -605,21 +603,19 @@ public Task AsTask() return GetTaskForValueTaskSource(Unsafe.As>(obj)); } - // A class to not have a static initializer directly on ValueTask - private static class OnCompleted - { - internal static readonly Action, object?, short, ValueTaskSourceOnCompletedFlags> s_action = - static (object obj, Action continuation, object? state, short token, ValueTaskSourceOnCompletedFlags flags) => - Unsafe.As>(obj).OnCompleted(continuation, state, token, flags); - } + /// + /// Helper to invoke IValueTaskSource.OnCompleted from a caller that does not know the actual type of the source. + /// + private static void OnCompleted(object obj, Action continuation, object? state, short token, ValueTaskSourceOnCompletedFlags flags) => + Unsafe.As>(obj).OnCompleted(continuation, state, token, flags); - internal object AsTaskOrNotifier() + internal unsafe object AsTaskOrNotifier() { object? obj = _obj; Debug.Assert(obj is Task || obj is IValueTaskSource); return obj as Task ?? - (object)ValueTaskSourceNotifier.GetInstance(obj, OnCompleted.s_action, _token); + (object)ValueTaskSourceNotifier.GetInstance(obj, &OnCompleted, _token); } /// Gets a that may be used at any point in the future. @@ -883,7 +879,7 @@ public ConfiguredValueTaskAwaitable ConfigureAwait(bool continueOnCaptu } } - internal sealed class ValueTaskSourceNotifier + internal unsafe sealed class ValueTaskSourceNotifier { // ValueTaskSourceNotifier is used only during suspension sequence, thus // a given thread will never need more than one instance. @@ -892,12 +888,12 @@ internal sealed class ValueTaskSourceNotifier private static ValueTaskSourceNotifier? t_instance; private object _source; - private Action, object?, short, ValueTaskSourceOnCompletedFlags> _onCompleted; + private delegate* managed, object?, short, ValueTaskSourceOnCompletedFlags, void> _onCompleted; private short _token; private ValueTaskSourceNotifier( object source, - Action, object?, short, ValueTaskSourceOnCompletedFlags> onCompleted, + delegate* managed, object?, short, ValueTaskSourceOnCompletedFlags, void> onCompleted, short token) { _source = source; @@ -907,7 +903,7 @@ private ValueTaskSourceNotifier( public static ValueTaskSourceNotifier GetInstance( object source, - Action, object?, short, ValueTaskSourceOnCompletedFlags> onCompleted, + delegate* managed, object?, short, ValueTaskSourceOnCompletedFlags, void> onCompleted, short token) { ValueTaskSourceNotifier? instance = t_instance; @@ -928,9 +924,8 @@ public void OnCompleted(Action continuation, object? state, ValueTaskSo _onCompleted(_source, continuation, state, _token, flags); // The data that we store is effectively single-use. - // Once used, clear the fields to not retain unknown data. - _source = _onCompleted = null!; - _token = 0; + // Once used, clear the _source to not retain unknown data. + _source = null!; } } }