As of C# 7.0 async methods can return ValueTask<T>. The explanation says that it should be used when we have a cached result or simulating async via synchronous code. However I still do not understand what is the problem with using ValueTask always or in fact why async/await wasn't built with a value type from the start. When would ValueTask fail to do the job?
From the API docs (emphasis added):
A method may return an instance of this value type when it's likely that the result of its operation will be available synchronously and when it's expected to be invoked so frequently that the cost of allocating a new
Task<TResult>
for each call will be prohibitive.There are tradeoffs to using a
ValueTask<TResult>
instead of aTask<TResult>
. For example, while aValueTask<TResult>
can help avoid an allocation in the case where the successful result is available synchronously, it also contains two fields whereas aTask<TResult>
as a reference type is a single field. This means that a method call ends up returning two fields worth of data instead of one, which is more data to copy. It also means that if a method that returns one of these is awaited within anasync
method, the state machine for thatasync
method will be larger due to needing to store the struct that's two fields instead of a single reference.Further, for uses other than consuming the result of an asynchronous operation via
await
,ValueTask<TResult>
can lead to a more convoluted programming model, which can in turn actually lead to more allocations. For example, consider a method that could return either aTask<TResult>
with a cached task as a common result or aValueTask<TResult>
. If the consumer of the result wants to use it as aTask<TResult>
, such as to use with in methods likeTask.WhenAll
andTask.WhenAny
, theValueTask<TResult>
would first need to be converted into aTask<TResult>
usingAsTask
, which leads to an allocation that would have been avoided if a cachedTask<TResult>
had been used in the first place.As such, the default choice for any asynchronous method should be to return a
Task
orTask<TResult>
. Only if performance analysis proves it worthwhile should aValueTask<TResult>
be used instead ofTask<TResult>
.