According to the documentation a ValueTask<TResult>
...
Provides a value type that wraps a
Task<TResult>
and aTResult
, only one of which is used.
My question is about the state machine that the C# compiler generates when the async
keyword is encountered. Is it smart enough to generate a ValueTask<TResult>
that wraps a TResult
, when the result is available immediately, or one that wraps a Task<TResult>
, when the result comes after an await
? Here is an example:
static async ValueTask<DateTime> GetNowAsync(bool withDelay)
{
if (withDelay) await Task.Delay(1000);
return DateTime.Now;
}
static void Test()
{
var t1 = GetNowAsync(false);
var t2 = GetNowAsync(true);
}
Calling GetNowAsync(false)
should return a TResult
wrapper, because nothing is awaited, and calling GetNowAsync(true)
should return a Task<TResult>
wrapper, because a Task.Delay
is awaited before the result becomes available. I am worried about the possibility that the state machine always returns Task
wrappers, nullifying all the advantages of the ValueTask
type over the Task
(and keeping all the disadvantages). As far as I can tell the properties of the type ValueTask<TResult>
offer no indication about what it wraps internally. I pasted the code above to sharplab.io, but the output didn't help me to answer this question either.
I guess I should answer my own question, since I know the answer now. The answer is that my worries were unwarranted: the C# compiler is smart enough to emit the right type of ValueTask<TResult>
in every case. It emits a value-wrapper when the result is synchronously available, and a task-wrapper when it isn't.
I came to this conclusion by performance measurements: by measuring the memory allocated in each case, and the time needed to create the same amount of tasks. The results are clear and consistent. For example a ValueTask<int>
consumes exactly 12 bytes when it wraps an int
value, and exactly 48 bytes when it wraps a Task<int>
, so there is no doubt about what's going on under the hoods.