Search code examples
c#async-awaittaskvaluetask

Why can an async method with return type ValueTask await a regular Task without generating a compiler error?


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().


Solution

  • 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 awaits 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.