Why do we need more than one `await` statement in a C# method?

Why do we need more than one await statement in a C# method?

You need as much await in your code, you want to (a)wait for the execution of the called async method completes. When you call an asynchronous method, it will (at some point!) return a task (incomplete or completed one), what is technically a promise from that method that at some point it will completes its job.

For example _bookRepository.InsertAsync(...) promise that it will insert the item into the repository and it will notify you via the returned Task when it is happened. It is now up to you, the caller, whether you want to wait for it using await, or you do not care if and when this job finished (fire and forget) so you do not use await, and continue to execute the rest of the caller code.

So it is totally valid to remove the await keywords almost everywhere but there is a very high chance it will alter the program flow and could lead to side effects (explanation in the next section).

In case we will remove the second and the third await statements in the SeedAsync no extra threads will be blocked, since already after the first await we are not blocking any useful threads and we already allocated an extra thread for the first await. So, by using the second and the third await statements we are allocating the extra two threads.

There are several misunderstanding here:

  • Calling an async method does not make the code asynchronous. The called code will run synchronously up until the called method, or any child method calls returns a Task, what is not already completed. Awaiting on completed task makes the call and the continuation completely synchronous!
  • Following up on the previous point, async method calls does not create or allocate thread per se. It is up to the called code to "side-load" its job one way or another.
  • Using await keyword will "put" the remaining code after the keyword into a continuation what will run asynchronously, but it say nothing about threads or necessarily creates one! It has to be imagined that the continuation is put into a queue and will be executed at some point by a thread.

there is must be a reason to the use of the three await statements instead of the one.

Without any further investigation into the project you quoted, most probably _bookRepository.InsertAsync(...) methods are not "parallel safe", otherwise await Task.WhenAll(insert1, insert2) format could have been used. Also not using await for the insertions potentially lead to side effect, for example multi threading like race conditions (read state before write has been finished).

EDIT: You can find lots of useful reading material on docs.microsoft.com, such this: https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/concepts/async/task-asynchronous-programming-model

I suggest to read them multiple times and make test apps because the topic is more complex than it looks like and filled with small details easy to misinterpret.


await will wait until the operation is not executed. So you has 2 async operations, that's why you need to use await.

One await for each async operation (method).

So, you have 3 async methods. You can call it without await, but it will crash. When you call it without await, it will start to execute in another thread, and thread where SeedAsync is executing, will not wait until InsertAsync is executed. It will start second InsertAsync at the same time

So, in your case, you can insert values without await. It will work. But in common case it's better to use await. Because often order of operations is important. await is allow to control the order of operations

Sometimes you need to run some tasks and then wait for all. Then you can use Task.WhenAll(t1,t2,t3)


If you remove the last two await, your code will become like this:

public async Task SeedAsync(DataSeedContext context)
{
    if (await _bookRepository.GetCountAsync() == 0)
    {
        _bookRepository.InsertAsync(new Book("Title1"));
        _bookRepository.InsertAsync(new Book("Title2"));
    }
}

Immediately you'll get two warnings:

Warning CS4014
Because this call is not awaited, execution of the current method continues before the call is completed. Consider applying the 'await' operator to the result of the call.

The message of the warning is descriptive enough. Without these two await:

  1. The two InsertAsync tasks will run concurrently. This could cause state corruption in case the InsertAsync manipulates shared state without synchronization.
  2. The callers of the SeedAsync method will receive a Task that will signal its completion before it is actually completed.
  3. Any unhandled exceptions that may occur during the execution of the two InsertAsync tasks will remain unobserved.

Tags:

C#

Async Await