Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Does await Task.CompletedTask mean the async method will run synchronously?

static async Task WaitTaskCompleted()
{
    //Use Thread A before await Task.CompletedTask
    await Task.CompletedTask;
    //Will the code after await Task.CompletedTask always use Thread A, or there is chance to have a Thread B?
}

which means await Task.CompletedTask will always actually perform the method synchronously?

like image 945
Scott.Hu Avatar asked Jan 16 '20 12:01

Scott.Hu


2 Answers

Yes, this code will always run synchronously; the main continuation compiler goo is only invoked when the first incomplete awaitable is encountered.

You can see this in sharplab - in particular, here:

awaiter = Task.CompletedTask.GetAwaiter();
if (!awaiter.IsCompleted)
{
    num = (<>1__state = 0);
    <>u__1 = awaiter;
    <>t__builder.AwaitUnsafeOnCompleted(ref awaiter, ref this);
    return;
}

It is the AwaitUnsafeOnCompleted(...) + return that implements asynchronicity.

like image 59
Marc Gravell Avatar answered Nov 15 '22 03:11

Marc Gravell


Just for the sanity of other readers, the usual purpose of Task.FromResult<T>, Task.CompletedTask, Task.FromCancelation and Task.FromException() is to provide simple factory methods for various types of Task (i.e. with / without return payload, or to return an exception or mimic cancellation), and in all cases, the tasks returned will be regarded as IsCompleted, as per the source:

private const int TASK_STATE_COMPLETED_MASK = TASK_STATE_CANCELED | TASK_STATE_FAULTED 
                                              | TASK_STATE_RAN_TO_COMPLETION;

As per @Marc's answer, awaiting an already IsCompleted Task short circuits the awaiter and execution will continue synchronously on the same thread.

As per my comment, it would be highly unusual to directly await a Task created by Task.CompletedTask or Task.FromResult, as this compiler would generate an unnecessary async state machine wrapper, which is total overkill in the OP's scenario.

A common scenario for using the various completed Task factory methods would be in mocking during Unit testing, where the class / interface being mocked requires a Task to be returned but otherwise has no need for async. For example, if the following production interface needed mocking or stubbing:

public interface ISomeInterface
{
     Task<DateTime> GetDateAsync();
}

which could be stubbed as follows:

public class MyMock : ISomeInterface
{
    public Task<DateTime> GetDateAsync() // no async
    {
        // Directly return a completed task return a fake result
        return Task.FromResult(new DateTime(2019, 11, 12, 0, 0, 0, DateTimeKind.Utc));
    }
}

The production class being tested (SUT) would then likely await the result of GetDateAsync() on the injected (and now mocked) ISomeInterface, and will usually be none the wiser that the called method just rubber stamped the Task and returned fake data synchronously.

like image 23
StuartLC Avatar answered Nov 15 '22 05:11

StuartLC