Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

await Task.CompletedTask vs return

I'm trying to understand the difference between await Task.CompletedTask and return but can't seem to find any clearly defined explanation.

Why / when would you use this:

public async Task Test()
{
    await Task.CompletedTask;
}

over this?

public async Task Test()
{
    return;
}

It's my understanding that both will have their status set to TaskStatus.RanToCompletion though I have the feeling it might have to do with physical time or something like that based on the explanation from Task.FromResult:

The method is commonly used when the return value of a task is immediately known without executing a longer code path.

Would appreciate a clear demystification of this as I've studied MS code on GitHub, the MS Docs as well as every link I could find but nowhere does it give a clear explanation. I also see await Task.CompletedTask at the end of larger methods which per some comments I found from MS on GitHub is actually in error as it's not supposed to contain that and they want to get it cleaned out of the repo.

If also a clear demystification of Task.FromResult (since they're siblings) that would be appreciated as I'm also still unclear of when to use:

public async Task<bool> Test()
{
    return await Task.FromResult(true);
}

over this:

public async Task<bool> Test()
{
    return true;
}
like image 382
Storm Avatar asked Sep 14 '20 13:09

Storm


People also ask

Should you return the task or await?

The only time we truly want to await is when we do something with the result of the async task in the continuation of the method. Note that if we don't have return await, but return a Task<T> instead, the return happens right away, so, if the code is inside a try/catch block, the exception will not be caught.

Should async method return task?

Async methods can have the following return types: Task, for an async method that performs an operation but returns no value. Task<TResult>, for an async method that returns a value. void , for an event handler.

What is the return type of await?

Await expressions make promise-returning functions behave as though they're synchronous by suspending execution until the returned promise is fulfilled or rejected. The resolved value of the promise is treated as the return value of the await expression.

How do I return a NULL from a task?

We can return null from a method that returns a Task because Task is a reference type. In our previous example, we return null from NonAsyncFoo() . But, awaiting null isn't legal, so await NonAsyncFoo() throws a NullReferenceException .


Video Answer


1 Answers

Let's look the question from the consumer-side.

If you define an interface, which imposes an operation that returns a Task then you don't say anything about how it will be calculated / executed (so, there is no async access modifier in the method signature). It is an implementation detail.

Interface

public interface ITest
{
    Task Test();
    Task<bool> IsTest();
}

So, it is up to you how you implement the interface.

You can do it in a synchronous way, when there won't be any AsyncStateMachine generated because of the absence of the async keyword.

Implementation #1

public class TestImpl : ITest
{
    public Task Test()
    {
        return Task.CompletedTask;
    }

    public Task<bool> IsTest()
    {
        return Task.FromResult(true);
    }
}

Or you can try to implement it in an asynchronous way but without await operators. Here you will receive CS1998 warnings.

Implementation #2

public class TestImpl : ITest
{
    public async Task Test()
    {
        return;
    }

    public async Task<bool> IsTest()
    {
        return true;
    }
}

This async method lacks 'await' operators and will run synchronously. Consider using the 'await' operator to await non-blocking API calls, or 'await Task.Run(...)' to do CPU-bound work on a background thread.

In other words this implementation does not define a state machine. An async method is divided into different states based on the await keywords:

  • before_the_await_1,
  • after_the_await_1_but_before_the_await_2
  • after_the_await_2_but_before_the_await_3
  • ...

If you haven't got any await then you would have a single state, which would run in sync (there is no need to preserve state, execute async operation and then call MoveNext()).


Or you can try to implement it in an asynchronous way with await operators.

Implementation #3

public class TestImpl : ITest
{
    public async Task Test()
    {
        await Task.CompletedTask;
    }

    public async Task<bool> IsTest()
    {
        return await Task.FromResult(true);
    }
}

In this case there will be an async state machine but it won't be allocated on the heap. By default it is a struct and if it finishes in sync then we don't need to allocate them on the heap to extend its life out of the scope of the method.

For further information please read these articles:

  • Async/await in .NET Core 3.x
  • Async ValueTask Pooling in .NET 5
  • Dissecting the async methods in C#
like image 179
Peter Csala Avatar answered Sep 17 '22 15:09

Peter Csala