From 7c1cfe088ef9def23f50cedfddd5306f83c3845b Mon Sep 17 00:00:00 2001 From: Jakob Botsch Nielsen Date: Wed, 3 Jun 2026 14:31:11 +0200 Subject: [PATCH 1/5] Opt pooled async methods out of runtime async We do not have a replacement for this concept in runtime async (yet). For now opt these methods out of runtime async. --- .../RuntimeAsyncMethodGenerationAttribute.cs | 10 ++++++++++ .../Threading/AsyncOverSyncWithIoCancellation.cs | 2 ++ .../System.IO.Pipelines/src/System.IO.Pipelines.csproj | 2 ++ .../src/System/IO/Pipelines/StreamPipeReader.cs | 1 + .../src/System/IO/Pipelines/StreamPipeWriter.cs | 1 + .../System.IO.Pipes/src/System.IO.Pipes.csproj | 2 ++ .../System.Net.Http/src/System.Net.Http.csproj | 2 ++ .../Net/Http/SocketsHttpHandler/HttpConnection.cs | 1 + .../Net/Http/SocketsHttpHandler/RawConnectionStream.cs | 1 + .../System.Net.Security/src/System.Net.Security.csproj | 2 ++ .../src/System/Net/Security/SslStream.IO.cs | 2 ++ .../src/System.Net.WebSockets.csproj | 2 ++ .../src/System/Net/WebSockets/ManagedWebSocket.cs | 2 ++ .../src/System.Private.CoreLib.Shared.projitems | 3 +++ .../System/IO/Strategies/BufferedFileStreamStrategy.cs | 2 ++ .../System.Private.CoreLib/src/System/IO/Stream.cs | 1 + 16 files changed, 36 insertions(+) create mode 100644 src/libraries/Common/src/System/Runtime/CompilerServices/RuntimeAsyncMethodGenerationAttribute.cs diff --git a/src/libraries/Common/src/System/Runtime/CompilerServices/RuntimeAsyncMethodGenerationAttribute.cs b/src/libraries/Common/src/System/Runtime/CompilerServices/RuntimeAsyncMethodGenerationAttribute.cs new file mode 100644 index 00000000000000..58b540a83a043e --- /dev/null +++ b/src/libraries/Common/src/System/Runtime/CompilerServices/RuntimeAsyncMethodGenerationAttribute.cs @@ -0,0 +1,10 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace System.Runtime.CompilerServices; + +[AttributeUsage(AttributeTargets.Method)] +internal sealed class RuntimeAsyncMethodGenerationAttribute(bool runtimeAsync) : Attribute +{ + public bool RuntimeAsync => runtimeAsync; +} diff --git a/src/libraries/Common/src/System/Threading/AsyncOverSyncWithIoCancellation.cs b/src/libraries/Common/src/System/Threading/AsyncOverSyncWithIoCancellation.cs index 4233fc4094cb14..d4a786d2449f68 100644 --- a/src/libraries/Common/src/System/Threading/AsyncOverSyncWithIoCancellation.cs +++ b/src/libraries/Common/src/System/Threading/AsyncOverSyncWithIoCancellation.cs @@ -82,6 +82,7 @@ private void Reset() /// performed by the function. /// [AsyncMethodBuilder(typeof(PoolingAsyncValueTaskMethodBuilder))] + [RuntimeAsyncMethodGeneration(false)] public static async ValueTask InvokeAsync(Action action, TState state, CancellationToken cancellationToken) { // Queue the work to complete asynchronously. Logically, this is just queueing a work item to the thread pool. @@ -120,6 +121,7 @@ public static async ValueTask InvokeAsync(Action action, TState /// performed by the function. /// [AsyncMethodBuilder(typeof(PoolingAsyncValueTaskMethodBuilder<>))] + [RuntimeAsyncMethodGeneration(false)] public static async ValueTask InvokeAsync(Func func, TState state, CancellationToken cancellationToken) { // Queue the work to complete asynchronously. Logically, this is just queueing a work item to the thread pool. diff --git a/src/libraries/System.IO.Pipelines/src/System.IO.Pipelines.csproj b/src/libraries/System.IO.Pipelines/src/System.IO.Pipelines.csproj index 0153a1de4f8437..93cf8ff65bf91a 100644 --- a/src/libraries/System.IO.Pipelines/src/System.IO.Pipelines.csproj +++ b/src/libraries/System.IO.Pipelines/src/System.IO.Pipelines.csproj @@ -50,6 +50,8 @@ System.IO.Pipelines.PipeReader + diff --git a/src/libraries/System.IO.Pipelines/src/System/IO/Pipelines/StreamPipeReader.cs b/src/libraries/System.IO.Pipelines/src/System/IO/Pipelines/StreamPipeReader.cs index b3b96992666b63..9b8684f750afc5 100644 --- a/src/libraries/System.IO.Pipelines/src/System/IO/Pipelines/StreamPipeReader.cs +++ b/src/libraries/System.IO.Pipelines/src/System/IO/Pipelines/StreamPipeReader.cs @@ -237,6 +237,7 @@ private ValueTask ReadInternalAsync(int? minimumSize, CancellationTo #if NET [AsyncMethodBuilder(typeof(PoolingAsyncValueTaskMethodBuilder<>))] + [RuntimeAsyncMethodGeneration(false)] #endif static async ValueTask Core(StreamPipeReader reader, int? minimumSize, CancellationTokenSource tokenSource, CancellationToken cancellationToken) { diff --git a/src/libraries/System.IO.Pipelines/src/System/IO/Pipelines/StreamPipeWriter.cs b/src/libraries/System.IO.Pipelines/src/System/IO/Pipelines/StreamPipeWriter.cs index 81ceb5484d12dd..874461af3363f5 100644 --- a/src/libraries/System.IO.Pipelines/src/System/IO/Pipelines/StreamPipeWriter.cs +++ b/src/libraries/System.IO.Pipelines/src/System/IO/Pipelines/StreamPipeWriter.cs @@ -288,6 +288,7 @@ private void Cancel() #if NET [AsyncMethodBuilder(typeof(PoolingAsyncValueTaskMethodBuilder<>))] + [RuntimeAsyncMethodGeneration(false)] #endif private async ValueTask FlushAsyncInternal(bool writeToStream, ReadOnlyMemory data, CancellationToken cancellationToken = default) { diff --git a/src/libraries/System.IO.Pipes/src/System.IO.Pipes.csproj b/src/libraries/System.IO.Pipes/src/System.IO.Pipes.csproj index 280212748fd613..896e706663e727 100644 --- a/src/libraries/System.IO.Pipes/src/System.IO.Pipes.csproj +++ b/src/libraries/System.IO.Pipes/src/System.IO.Pipes.csproj @@ -112,6 +112,8 @@ Link="Common\Interop\Windows\Interop.GetCurrentThreadId.cs" /> + diff --git a/src/libraries/System.Net.Http/src/System.Net.Http.csproj b/src/libraries/System.Net.Http/src/System.Net.Http.csproj index 06c5430d2db5c7..284f434945b94f 100644 --- a/src/libraries/System.Net.Http/src/System.Net.Http.csproj +++ b/src/libraries/System.Net.Http/src/System.Net.Http.csproj @@ -126,6 +126,8 @@ + ReadBufferedAsync(Memory destination) } [AsyncMethodBuilder(typeof(PoolingAsyncValueTaskMethodBuilder<>))] + [RuntimeAsyncMethodGeneration(false)] private async ValueTask ReadBufferedAsyncCore(Memory destination) { // This is called when reading the response body. diff --git a/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/RawConnectionStream.cs b/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/RawConnectionStream.cs index 15ff3ad465511c..dd572897e0d59d 100644 --- a/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/RawConnectionStream.cs +++ b/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/RawConnectionStream.cs @@ -42,6 +42,7 @@ public override int Read(Span buffer) } [AsyncMethodBuilder(typeof(PoolingAsyncValueTaskMethodBuilder<>))] + [RuntimeAsyncMethodGeneration(false)] public override async ValueTask ReadAsync(Memory buffer, CancellationToken cancellationToken) { CancellationHelper.ThrowIfCancellationRequested(cancellationToken); diff --git a/src/libraries/System.Net.Security/src/System.Net.Security.csproj b/src/libraries/System.Net.Security/src/System.Net.Security.csproj index 1b208fca00a699..577bcccf801c5d 100644 --- a/src/libraries/System.Net.Security/src/System.Net.Security.csproj +++ b/src/libraries/System.Net.Security/src/System.Net.Security.csproj @@ -34,6 +34,8 @@ Link="Common\System\LocalAppContextSwitches.Common.cs" /> + diff --git a/src/libraries/System.Net.Security/src/System/Net/Security/SslStream.IO.cs b/src/libraries/System.Net.Security/src/System/Net/Security/SslStream.IO.cs index 9ee1c4519b234f..a2c1aa1200bfec 100644 --- a/src/libraries/System.Net.Security/src/System/Net/Security/SslStream.IO.cs +++ b/src/libraries/System.Net.Security/src/System/Net/Security/SslStream.IO.cs @@ -791,6 +791,7 @@ private bool HaveFullTlsFrame(out int frameSize) } [AsyncMethodBuilder(typeof(PoolingAsyncValueTaskMethodBuilder<>))] + [RuntimeAsyncMethodGeneration(false)] private async ValueTask EnsureFullTlsFrameAsync(CancellationToken cancellationToken, int estimatedSize) where TIOAdapter : IReadWriteAdapter { @@ -839,6 +840,7 @@ private async ValueTask EnsureFullTlsFrameAsync(CancellationTok } [AsyncMethodBuilder(typeof(PoolingAsyncValueTaskMethodBuilder<>))] + [RuntimeAsyncMethodGeneration(false)] private async ValueTask ReadAsyncInternal(Memory buffer, CancellationToken cancellationToken) where TIOAdapter : IReadWriteAdapter { diff --git a/src/libraries/System.Net.WebSockets/src/System.Net.WebSockets.csproj b/src/libraries/System.Net.WebSockets/src/System.Net.WebSockets.csproj index 4b1229be09da56..6725eba0e435fb 100644 --- a/src/libraries/System.Net.WebSockets/src/System.Net.WebSockets.csproj +++ b/src/libraries/System.Net.WebSockets/src/System.Net.WebSockets.csproj @@ -52,6 +52,8 @@ Link="Common\System\Net\Logging\NetEventSource.Common.cs" /> + diff --git a/src/libraries/System.Net.WebSockets/src/System/Net/WebSockets/ManagedWebSocket.cs b/src/libraries/System.Net.WebSockets/src/System/Net/WebSockets/ManagedWebSocket.cs index 388c660cd01d39..47188efe5cfd7f 100644 --- a/src/libraries/System.Net.WebSockets/src/System/Net/WebSockets/ManagedWebSocket.cs +++ b/src/libraries/System.Net.WebSockets/src/System/Net/WebSockets/ManagedWebSocket.cs @@ -771,6 +771,7 @@ private static void WriteRandomMask(byte[] buffer, int offset) => /// The CancellationToken used to cancel the websocket. /// Information about the received message. [AsyncMethodBuilder(typeof(PoolingAsyncValueTaskMethodBuilder<>))] + [RuntimeAsyncMethodGeneration(false)] private async ValueTask ReceiveAsyncPrivate(Memory payloadBuffer, CancellationToken cancellationToken) { // This is a long method. While splitting it up into pieces would arguably help with readability, doing so would @@ -1599,6 +1600,7 @@ private void ConsumeFromBuffer(int count) } [AsyncMethodBuilder(typeof(PoolingAsyncValueTaskMethodBuilder))] + [RuntimeAsyncMethodGeneration(false)] private async ValueTask EnsureBufferContainsAsync(int minimumRequiredBytes, CancellationToken cancellationToken) { Debug.Assert(minimumRequiredBytes <= _receiveBuffer.Length, $"Requested number of bytes {minimumRequiredBytes} must not exceed {_receiveBuffer.Length}"); diff --git a/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems b/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems index 7e9355d26d52a0..018d51988bf715 100644 --- a/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems +++ b/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems @@ -2315,6 +2315,9 @@ Common\System\Threading\AsyncOverSyncWithIoCancellation.cs + + Common\Runtime\CompilerServices\RuntimeAsyncMethodGenerationAttribute.cs + diff --git a/src/libraries/System.Private.CoreLib/src/System/IO/Strategies/BufferedFileStreamStrategy.cs b/src/libraries/System.Private.CoreLib/src/System/IO/Strategies/BufferedFileStreamStrategy.cs index 090f595db1097e..cfdbd596199ade 100644 --- a/src/libraries/System.Private.CoreLib/src/System/IO/Strategies/BufferedFileStreamStrategy.cs +++ b/src/libraries/System.Private.CoreLib/src/System/IO/Strategies/BufferedFileStreamStrategy.cs @@ -388,6 +388,7 @@ private async ValueTask ReadFromNonSeekableAsync(Memory destination, } [AsyncMethodBuilder(typeof(PoolingAsyncValueTaskMethodBuilder<>))] + [RuntimeAsyncMethodGeneration(false)] private async ValueTask ReadAsyncSlowPath(Task semaphoreLockTask, Memory buffer, CancellationToken cancellationToken) { Debug.Assert(_asyncActiveSemaphore != null); @@ -671,6 +672,7 @@ private async ValueTask WriteToNonSeekableAsync(ReadOnlyMemory source, Can } [AsyncMethodBuilder(typeof(PoolingAsyncValueTaskMethodBuilder))] + [RuntimeAsyncMethodGeneration(false)] private async ValueTask WriteAsyncSlowPath(Task semaphoreLockTask, ReadOnlyMemory source, CancellationToken cancellationToken) { Debug.Assert(_asyncActiveSemaphore != null); diff --git a/src/libraries/System.Private.CoreLib/src/System/IO/Stream.cs b/src/libraries/System.Private.CoreLib/src/System/IO/Stream.cs index b99ff4886eb0be..b04fae4200e37e 100644 --- a/src/libraries/System.Private.CoreLib/src/System/IO/Stream.cs +++ b/src/libraries/System.Private.CoreLib/src/System/IO/Stream.cs @@ -424,6 +424,7 @@ public ValueTask ReadAtLeastAsync(Memory buffer, int minimumBytes, bo // No argument checking is done here. It is up to the caller. [AsyncMethodBuilder(typeof(PoolingAsyncValueTaskMethodBuilder<>))] + [RuntimeAsyncMethodGeneration(false)] private async ValueTask ReadAtLeastAsyncCore(Memory buffer, int minimumBytes, bool throwOnEndOfStream, CancellationToken cancellationToken) { Debug.Assert(minimumBytes <= buffer.Length); From 2226e6bab37d7a4089aef17a0367530c793b22f7 Mon Sep 17 00:00:00 2001 From: Jakob Botsch Nielsen Date: Wed, 3 Jun 2026 14:42:38 +0200 Subject: [PATCH 2/5] Whitespace --- .../CompilerServices/RuntimeAsyncMethodGenerationAttribute.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libraries/Common/src/System/Runtime/CompilerServices/RuntimeAsyncMethodGenerationAttribute.cs b/src/libraries/Common/src/System/Runtime/CompilerServices/RuntimeAsyncMethodGenerationAttribute.cs index 58b540a83a043e..fa3d08efa22abc 100644 --- a/src/libraries/Common/src/System/Runtime/CompilerServices/RuntimeAsyncMethodGenerationAttribute.cs +++ b/src/libraries/Common/src/System/Runtime/CompilerServices/RuntimeAsyncMethodGenerationAttribute.cs @@ -6,5 +6,5 @@ namespace System.Runtime.CompilerServices; [AttributeUsage(AttributeTargets.Method)] internal sealed class RuntimeAsyncMethodGenerationAttribute(bool runtimeAsync) : Attribute { - public bool RuntimeAsync => runtimeAsync; + public bool RuntimeAsync => runtimeAsync; } From c8109dc2fd2606ed8993090c6cd986d4a4b9bb4e Mon Sep 17 00:00:00 2001 From: Jakob Botsch Nielsen Date: Wed, 3 Jun 2026 15:04:54 +0200 Subject: [PATCH 3/5] Feedback --- .../src/System.Private.CoreLib.Shared.projitems | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems b/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems index 018d51988bf715..dae1fa6fa5e9d2 100644 --- a/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems +++ b/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems @@ -2316,7 +2316,7 @@ Common\System\Threading\AsyncOverSyncWithIoCancellation.cs - Common\Runtime\CompilerServices\RuntimeAsyncMethodGenerationAttribute.cs + Common\System\Runtime\CompilerServices\RuntimeAsyncMethodGenerationAttribute.cs From 214d6c5af25f8e561695860b217d91476640c0cc Mon Sep 17 00:00:00 2001 From: Jakob Botsch Nielsen Date: Wed, 3 Jun 2026 15:34:34 +0200 Subject: [PATCH 4/5] Fix --- .../src/System.Private.CoreLib.Shared.projitems | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems b/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems index dae1fa6fa5e9d2..11fd13aa9d97e5 100644 --- a/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems +++ b/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems @@ -1560,6 +1560,9 @@ Common\System\Reflection\Metadata\TypeNameParserHelpers.cs + + Common\System\Runtime\CompilerServices\RuntimeAsyncMethodGenerationAttribute.cs + Common\System\Runtime\Versioning\NonVersionableAttribute.cs @@ -2315,9 +2318,6 @@ Common\System\Threading\AsyncOverSyncWithIoCancellation.cs - - Common\System\Runtime\CompilerServices\RuntimeAsyncMethodGenerationAttribute.cs - From 91d23275ddd9e23d23b8ad58c781d6460ea71034 Mon Sep 17 00:00:00 2001 From: Jakob Botsch Nielsen Date: Mon, 8 Jun 2026 13:01:06 +0200 Subject: [PATCH 5/5] Copilot feedback --- .../CompilerServices/RuntimeAsyncMethodGenerationAttribute.cs | 2 +- .../System.IO.Pipelines/src/System.IO.Pipelines.csproj | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/libraries/Common/src/System/Runtime/CompilerServices/RuntimeAsyncMethodGenerationAttribute.cs b/src/libraries/Common/src/System/Runtime/CompilerServices/RuntimeAsyncMethodGenerationAttribute.cs index fa3d08efa22abc..6152da7b384ed2 100644 --- a/src/libraries/Common/src/System/Runtime/CompilerServices/RuntimeAsyncMethodGenerationAttribute.cs +++ b/src/libraries/Common/src/System/Runtime/CompilerServices/RuntimeAsyncMethodGenerationAttribute.cs @@ -3,7 +3,7 @@ namespace System.Runtime.CompilerServices; -[AttributeUsage(AttributeTargets.Method)] +[AttributeUsage(AttributeTargets.Method, Inherited = false, AllowMultiple = false)] internal sealed class RuntimeAsyncMethodGenerationAttribute(bool runtimeAsync) : Attribute { public bool RuntimeAsync => runtimeAsync; diff --git a/src/libraries/System.IO.Pipelines/src/System.IO.Pipelines.csproj b/src/libraries/System.IO.Pipelines/src/System.IO.Pipelines.csproj index 93cf8ff65bf91a..e0cb743c86e261 100644 --- a/src/libraries/System.IO.Pipelines/src/System.IO.Pipelines.csproj +++ b/src/libraries/System.IO.Pipelines/src/System.IO.Pipelines.csproj @@ -51,7 +51,7 @@ System.IO.Pipelines.PipeReader + Link="Common\System\Runtime\CompilerServices\RuntimeAsyncMethodGenerationAttribute.cs" />