Say I have a method signature like this:
public async ValueTask<int> GetSomeValueAsync(int number);
Internally it may make an async HTTP call but it caches the result so next time it is called with the same number it can return the value from cache without the async call. Thus it returns a ValueTask as this avoids the overhead of creating a Task for the synchronous path of execution.
My question is, the caller of this method, if it makes no other async calls, should the signature be a Task or should ValueTask be propagated all the way up the call stack?
E.g. should it be this:
public async Task<int> CallingMethodAsync(int number)
{
return await GetSomeValueAsync(number) + 1;
}
Or should it be:
public async ValueTask<int> CallingMethodAsync(int number)
{
return await GetSomeValueAsync(number) + 1;
}
(examples above are simplified to illustrate the point)
For clarity, this question is not about whether GetSomeValueAsync() should return ValueTask or Task. I understand there are situations for both but take it as such that this evaluation has been made the outcome of that has correctly determined it should be ValueTask. The essence of the question is, should the caller of this method propagate up ValueTask in it's signature if it itself has no other async method call. Hopefully this clarification explains why the question, does not answer it.
Assuming that the
GetSomeValueAsyncis expected to complete synchronously most of the time, and theCallingMethodAsyncdoesn'tawaitanything else other than theGetSomeValueAsync, and also your goal is to minimize the memory allocations, then theValueTask<TResult>is preferable.That's because when a
ValueTask<TResult>is already completed upon creation, it wraps internally aTResult, not aTask<TResult>, so it doesn't allocate anything. On the contrary if you return aTask<TResult>, a new object will always have to be created, unless it happens to be one of the few type+value combinations that are internally cached by the .NET runtime itself, like aTask<int>with a value between -1 and 9 (see the internal staticTaskCacheclass).Online demo.
In my PC running on .NET 7 Release mode it outputs: