Skip to content

Commit 2784aad

Browse files
authored
Add AsyncMethodBuilderOverride and PoolingAsyncValueTaskMethodBuilders (#50116)
* Add AsyncMethodBuilderOverride and PoolingAsyncValueTaskMethodBuilders * Revise based on C# LDM changes to model * Fix API compat errors
1 parent b47094d commit 2784aad

12 files changed

+1242
-584
lines changed

src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -693,6 +693,8 @@
693693
<Compile Include="$(MSBuildThisFileDirectory)System\Runtime\CompilerServices\MethodImplOptions.cs" />
694694
<Compile Include="$(MSBuildThisFileDirectory)System\Runtime\CompilerServices\ModuleInitializerAttribute.cs" />
695695
<Compile Include="$(MSBuildThisFileDirectory)System\Runtime\CompilerServices\ReferenceAssemblyAttribute.cs" />
696+
<Compile Include="$(MSBuildThisFileDirectory)System\Runtime\CompilerServices\PoolingAsyncValueTaskMethodBuilder.cs" />
697+
<Compile Include="$(MSBuildThisFileDirectory)System\Runtime\CompilerServices\PoolingAsyncValueTaskMethodBuilderT.cs" />
696698
<Compile Include="$(MSBuildThisFileDirectory)System\Runtime\CompilerServices\PreserveBaseOverridesAttribute.cs" />
697699
<Compile Include="$(MSBuildThisFileDirectory)System\Runtime\CompilerServices\RuntimeCompatibilityAttribute.cs" />
698700
<Compile Include="$(MSBuildThisFileDirectory)System\Runtime\CompilerServices\RuntimeFeature.cs" />

src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/AsyncMethodBuilderAttribute.cs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,10 @@ namespace System.Runtime.CompilerServices
55
{
66
/// <summary>
77
/// Indicates the type of the async method builder that should be used by a language compiler to
8-
/// build the attributed type when used as the return type of an async method.
8+
/// build the attributed async method or to build the attributed type when used as the return type
9+
/// of an async method.
910
/// </summary>
10-
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Interface | AttributeTargets.Delegate | AttributeTargets.Enum, Inherited = false, AllowMultiple = false)]
11+
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Interface | AttributeTargets.Delegate | AttributeTargets.Enum | AttributeTargets.Method, Inherited = false, AllowMultiple = false)]
1112
public sealed class AsyncMethodBuilderAttribute : Attribute
1213
{
1314
/// <summary>Initializes the <see cref="AsyncMethodBuilderAttribute"/>.</summary>

src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/AsyncValueTaskMethodBuilder.cs

Lines changed: 16 additions & 98 deletions
Original file line numberDiff line numberDiff line change
@@ -3,33 +3,21 @@
33

44
using System.Runtime.InteropServices;
55
using System.Threading.Tasks;
6-
using Internal.Runtime.CompilerServices;
7-
8-
using StateMachineBox = System.Runtime.CompilerServices.AsyncValueTaskMethodBuilder<System.Threading.Tasks.VoidTaskResult>.StateMachineBox;
96

107
namespace System.Runtime.CompilerServices
118
{
129
/// <summary>Represents a builder for asynchronous methods that return a <see cref="ValueTask"/>.</summary>
1310
[StructLayout(LayoutKind.Auto)]
1411
public struct AsyncValueTaskMethodBuilder
1512
{
16-
/// <summary>true if we should use reusable boxes for async completions of ValueTask methods; false if we should use tasks.</summary>
17-
/// <remarks>
18-
/// We rely on tiered compilation turning this into a const and doing dead code elimination to make checks on this efficient.
19-
/// It's also required for safety that this value never changes once observed, as Unsafe.As casts are employed based on its value.
20-
/// </remarks>
21-
internal static readonly bool s_valueTaskPoolingEnabled = GetPoolAsyncValueTasksSwitch();
22-
/// <summary>Maximum number of boxes that are allowed to be cached per state machine type.</summary>
23-
internal static readonly int s_valueTaskPoolingCacheSize = GetPoolAsyncValueTasksLimitValue();
24-
2513
/// <summary>Sentinel object used to indicate that the builder completed synchronously and successfully.</summary>
26-
private static readonly object s_syncSuccessSentinel = AsyncValueTaskMethodBuilder<VoidTaskResult>.s_syncSuccessSentinel;
14+
private static readonly Task<VoidTaskResult> s_syncSuccessSentinel = AsyncValueTaskMethodBuilder<VoidTaskResult>.s_syncSuccessSentinel;
2715

28-
/// <summary>The wrapped state machine box or task, based on the value of <see cref="s_valueTaskPoolingEnabled"/>.</summary>
16+
/// <summary>The wrapped task.</summary>
2917
/// <remarks>
3018
/// If the operation completed synchronously and successfully, this will be <see cref="s_syncSuccessSentinel"/>.
3119
/// </remarks>
32-
private object? m_task; // Debugger depends on the exact name of this field.
20+
private Task<VoidTaskResult>? m_task; // Debugger depends on the exact name of this field.
3321

3422
/// <summary>Creates an instance of the <see cref="AsyncValueTaskMethodBuilder"/> struct.</summary>
3523
/// <returns>The initialized instance.</returns>
@@ -39,8 +27,7 @@ public struct AsyncValueTaskMethodBuilder
3927
/// <typeparam name="TStateMachine">The type of the state machine.</typeparam>
4028
/// <param name="stateMachine">The state machine instance, passed by reference.</param>
4129
[MethodImpl(MethodImplOptions.AggressiveInlining)]
42-
public void Start<TStateMachine>(ref TStateMachine stateMachine)
43-
where TStateMachine : IAsyncStateMachine =>
30+
public void Start<TStateMachine>(ref TStateMachine stateMachine) where TStateMachine : IAsyncStateMachine =>
4431
AsyncMethodBuilderCore.Start(ref stateMachine);
4532

4633
/// <summary>Associates the builder with the specified state machine.</summary>
@@ -55,29 +42,16 @@ public void SetResult()
5542
{
5643
m_task = s_syncSuccessSentinel;
5744
}
58-
else if (s_valueTaskPoolingEnabled)
59-
{
60-
Unsafe.As<StateMachineBox>(m_task).SetResult(default);
61-
}
6245
else
6346
{
64-
AsyncTaskMethodBuilder<VoidTaskResult>.SetExistingTaskResult(Unsafe.As<Task<VoidTaskResult>>(m_task), default);
47+
AsyncTaskMethodBuilder<VoidTaskResult>.SetExistingTaskResult(m_task, default);
6548
}
6649
}
6750

6851
/// <summary>Marks the task as failed and binds the specified exception to the task.</summary>
6952
/// <param name="exception">The exception to bind to the task.</param>
70-
public void SetException(Exception exception)
71-
{
72-
if (s_valueTaskPoolingEnabled)
73-
{
74-
AsyncValueTaskMethodBuilder<VoidTaskResult>.SetException(exception, ref Unsafe.As<object?, StateMachineBox?>(ref m_task));
75-
}
76-
else
77-
{
78-
AsyncTaskMethodBuilder<VoidTaskResult>.SetException(exception, ref Unsafe.As<object?, Task<VoidTaskResult>?>(ref m_task));
79-
}
80-
}
53+
public void SetException(Exception exception) =>
54+
AsyncTaskMethodBuilder<VoidTaskResult>.SetException(exception, ref m_task);
8155

8256
/// <summary>Gets the task for this builder.</summary>
8357
public ValueTask Task
@@ -94,27 +68,11 @@ public ValueTask Task
9468
// or it should be completing asynchronously, in which case AwaitUnsafeOnCompleted would have similarly
9569
// initialized m_task to a state machine object. However, if the type is used manually (not via
9670
// compiler-generated code) and accesses Task directly, we force it to be initialized. Things will then
97-
// "work" but in a degraded mode, as we don't know the TStateMachine type here, and thus we use a box around
98-
// the interface instead.
71+
// "work" but in a degraded mode, as we don't know the TStateMachine type here, and thus we use a normal
72+
// task object instead.
9973

100-
if (s_valueTaskPoolingEnabled)
101-
{
102-
var box = Unsafe.As<StateMachineBox?>(m_task);
103-
if (box is null)
104-
{
105-
m_task = box = AsyncValueTaskMethodBuilder<VoidTaskResult>.CreateWeaklyTypedStateMachineBox();
106-
}
107-
return new ValueTask(box, box.Version);
108-
}
109-
else
110-
{
111-
var task = Unsafe.As<Task<VoidTaskResult>?>(m_task);
112-
if (task is null)
113-
{
114-
m_task = task = new Task<VoidTaskResult>(); // base task used rather than box to minimize size when used as manual promise
115-
}
116-
return new ValueTask(task);
117-
}
74+
Task<VoidTaskResult>? task = m_task ??= new Task<VoidTaskResult>(); // base task used rather than box to minimize size when used as manual promise
75+
return new ValueTask(task);
11876
}
11977
}
12078

@@ -125,17 +83,8 @@ public ValueTask Task
12583
/// <param name="stateMachine">The state machine.</param>
12684
public void AwaitOnCompleted<TAwaiter, TStateMachine>(ref TAwaiter awaiter, ref TStateMachine stateMachine)
12785
where TAwaiter : INotifyCompletion
128-
where TStateMachine : IAsyncStateMachine
129-
{
130-
if (s_valueTaskPoolingEnabled)
131-
{
132-
AsyncValueTaskMethodBuilder<VoidTaskResult>.AwaitOnCompleted(ref awaiter, ref stateMachine, ref Unsafe.As<object?, StateMachineBox?>(ref m_task));
133-
}
134-
else
135-
{
136-
AsyncTaskMethodBuilder<VoidTaskResult>.AwaitOnCompleted(ref awaiter, ref stateMachine, ref Unsafe.As<object?, Task<VoidTaskResult>?>(ref m_task));
137-
}
138-
}
86+
where TStateMachine : IAsyncStateMachine =>
87+
AsyncTaskMethodBuilder<VoidTaskResult>.AwaitOnCompleted(ref awaiter, ref stateMachine, ref m_task);
13988

14089
/// <summary>Schedules the state machine to proceed to the next action when the specified awaiter completes.</summary>
14190
/// <typeparam name="TAwaiter">The type of the awaiter.</typeparam>
@@ -145,17 +94,8 @@ public void AwaitOnCompleted<TAwaiter, TStateMachine>(ref TAwaiter awaiter, ref
14594
[MethodImpl(MethodImplOptions.AggressiveInlining)]
14695
public void AwaitUnsafeOnCompleted<TAwaiter, TStateMachine>(ref TAwaiter awaiter, ref TStateMachine stateMachine)
14796
where TAwaiter : ICriticalNotifyCompletion
148-
where TStateMachine : IAsyncStateMachine
149-
{
150-
if (s_valueTaskPoolingEnabled)
151-
{
152-
AsyncValueTaskMethodBuilder<VoidTaskResult>.AwaitUnsafeOnCompleted(ref awaiter, ref stateMachine, ref Unsafe.As<object?, StateMachineBox?>(ref m_task));
153-
}
154-
else
155-
{
156-
AsyncTaskMethodBuilder<VoidTaskResult>.AwaitUnsafeOnCompleted(ref awaiter, ref stateMachine, ref Unsafe.As<object?, Task<VoidTaskResult>?>(ref m_task));
157-
}
158-
}
97+
where TStateMachine : IAsyncStateMachine =>
98+
AsyncTaskMethodBuilder<VoidTaskResult>.AwaitUnsafeOnCompleted(ref awaiter, ref stateMachine, ref m_task);
15999

160100
/// <summary>
161101
/// Gets an object that may be used to uniquely identify this builder to the debugger.
@@ -165,28 +105,6 @@ public void AwaitUnsafeOnCompleted<TAwaiter, TStateMachine>(ref TAwaiter awaiter
165105
/// It must only be used by the debugger and tracing purposes, and only in a single-threaded manner
166106
/// when no other threads are in the middle of accessing this or other members that lazily initialize the box.
167107
/// </remarks>
168-
internal object ObjectIdForDebugger
169-
{
170-
get
171-
{
172-
if (m_task is null)
173-
{
174-
m_task = s_valueTaskPoolingEnabled ? (object)
175-
AsyncValueTaskMethodBuilder<VoidTaskResult>.CreateWeaklyTypedStateMachineBox() :
176-
AsyncTaskMethodBuilder<VoidTaskResult>.CreateWeaklyTypedStateMachineBox();
177-
}
178-
179-
return m_task;
180-
}
181-
}
182-
183-
private static bool GetPoolAsyncValueTasksSwitch() =>
184-
Environment.GetEnvironmentVariable("DOTNET_SYSTEM_THREADING_POOLASYNCVALUETASKS") is string value &&
185-
(bool.IsTrueStringIgnoreCase(value) || value == "1");
186-
187-
private static int GetPoolAsyncValueTasksLimitValue() =>
188-
int.TryParse(Environment.GetEnvironmentVariable("DOTNET_SYSTEM_THREADING_POOLASYNCVALUETASKSLIMIT"), out int result) && result > 0 ?
189-
result :
190-
Environment.ProcessorCount * 4; // arbitrary default value
108+
internal object ObjectIdForDebugger => m_task ??= AsyncTaskMethodBuilder<VoidTaskResult>.CreateWeaklyTypedStateMachineBox();
191109
}
192110
}

0 commit comments

Comments
 (0)