The following code does not compile
public ValueTask Foo()
{
return Task.Delay(1000);
}
but yields an Error CS0029: Cannot implicitly convert type 'System.Threading.Tasks.Task' to 'System.Threading.Tasks.ValueTask
as expected.
However, this
public async ValueTask Bar()
{
await Task.Delay(1000);
}
does compile fine.
I was just wondering how this works. Is this all down to compiler magic and its async-await syntactic sugar or is there something else going on?
For context: I came across this when implementing IAsyncDisposable.DisposeAsync()
.
Is this all down to compiler magic and its async-await syntactic sugar?
In short, yes. Whenever you await
, the compiler needs to generate a state machine for that method. The task returned from the method then, is one that "represents" the state machine, rather than the single task that you are awaiting.
As a result, it doesn't matter what tasks you are awaiting anymore. The compiler just has to build the state machine according to where your await
s are in your method, and then build a new task.
Compare the code generated from the following snippets on SharpLab:
1:
async Task Bar()
{
await Task.Delay(1000);
}
2:
async ValueTask Bar()
{
await Task.Delay(1000);
}
The only substantial difference is that one uses AsyncTaskMethodBuilder
to build the task being returned, and the other using AsyncValueTaskMethodBuilder
.
For more details about the difference of awaiting a task vs directly returning the task, see this chain of duplicates.