EDIT: Proposal added 11/5/2020:
https://github.com/dotnet/csharplang/blob/master/proposals/csharp-10.0/async-method-builders.md
AsyncMethodBuilderAttribute can be put on a type to be used as the return type of an async method, e.g.
public struct AsyncCoolTypeMethodBuilder { public static AsyncCoolTypeMethodBuilder Create(); ... } [AsyncMethodBuilder(typeof(AsyncCoolTypeMethodBuilder))] public struct CoolType { ... } public async CoolType SomeMethodAsync() { ... } // will implicitly use AsyncCoolTypeMethodBuilder.Create()
This, however, means that:
Two parts:
this
for instance methods. The compiler will then forward the arguments to the method's invocation into the builder's Create.There are four types in .NET with built-in builders: Task
, ValueTask
, Task<T>
, and ValueTask<T>
. In .NET Core, significant work has gone in to optimizing async methods with these types, and thus in the majority of uses, each async method invocation will incur at most one allocation of overhead, e.g. a synchronously completing async method returning ValueTask<T>
won’t have any additional allocations, and an asynchronously completing async method returning ValueTask<T>
will incur one allocation for the underlying state machine object.
However, with this feature, it would be possible to avoid even that allocation, for developers/scenarios where it really mattered. .NET Core 2.1 sees the introduction of IValueTaskSource
and IValueTaskSource<T>
. Previously ValueTask<T>
could be constructed from a T
or a Task<T>
; now it can also be constructed from an IValueTaskSource<T>
. That means a ValueTask<T>
can be wrapped around an arbitrary backing implementation, and that backing implementation can be reused or pooled (.NET Core takes advantage of this in a variety of places, for example enabling allocation-free async sends and receives on sockets). However, there is no way currently for an async ValueTask<T>
method to utilize a custom IValueTaskSource<T>
to back it, because the builder can only be assigned by the developer of the ValueTask<T>
type; thus it can’t be customized for other uses.
If a developer could write:
[AsyncMethodBuilder(UseMyCustomValueTaskSourceMethodBuilder)] public async ValueTask<T> SomeMethodAsync() { … }
then the developer could write their own builder that used their own IValueTaskSource<T>
under the covers, enabling them to plug in arbitrary logic and even to pool.
However, such a pool would end up being shared across all uses of SomeMethodAsync. This could be a significant scalability bottleneck. If a pool for this were being managed manually, a developer would be able to make the pool specific to a particular instance rather than shared globally. For example, WebSocket
’s ValueTask<…> ReceiveAsync(…)
method utilizing this feature would end up hitting a pool shared by all WebSocket
instances for this particular WebSocket-derived type, but given WebSocket
’s constraints that only one receive can be in flight at a time, WebSocket
can have a very efficient “pool” of a single IValueTaskSource<T>
that’s reused over and over. To enable that, the compiler could pass the this
into UseMyCustomValueTaskSourceMethodBuilder
’s Create
method, e.g.
internal sealed class ManagedWebSocket : WebSocket { private struct WebSocketReceiveMethodBuilder { private ManagedWebSocket _webSocket; public static WebSocketReceiveMethodBuilder Create(ManagedWebSocket thisRef, Memory<byte> buffer, CancellationToken cancellationToken) => new WebSocketReceiveMethodBuilder { _webSocket = thisRef }; … } private IValueTaskSource<ValueWebSocketReceiveResult> _receiveVtsSingleton; [AsyncMethodBuilder(typeof(WebSocketReceiveMethodBuilder)] public override async ValueTask<ValueWebSocketReceiveResult> ReceiveAsync(Memory<byte> buffer, CancellationToken cancellationToken) { … } }
The ReceiveAsync implementation can then be written using awaits, and the builder can use _webSocket._receiveVtsSingleton
as the cache for the single instance it creates. As is now done in .NET Core for task’s builder, it can create an IValueTaskSource<ValueWebSocketReceiveResult>
that stores the state machine onto it as a strongly-typed property, avoiding a separate boxing allocation for it. Thus all state for the async operation becomes reusable across multiple ReceiveAsync invocations, resulting in amortized allocation-free calls.
https://github.com/dotnet/corefx/issues/27445
dotnet/coreclr#16618
dotnet/corefx#27497
ufcpp, jnm2, Unknown6656, bbarry, NinoFloris and 40 moreryanerdmann, juepiezhongren, quinmars, benaadams, skolima and 11 moreAnarchyMob, Nucs, HalidCisse, Gekctek and BrunoJuchli
RetroSearch is an open source project built by @garambo | Open a GitHub Issue
Search and Browse the WWW like it's 1997 | Search results from DuckDuckGo
HTML:
3.2
| Encoding:
UTF-8
| Version:
0.7.4