Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

Tags:

c#

async-await

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

E.g. here we have three await statements:

using System;
using System.Threading.Tasks;
using Volo.Abp.Data;
using Volo.Abp.DependencyInjection;
using Volo.Abp.Domain.Repositories;

namespace Acme.BookStore
{
    public class BookStoreDataSeederContributor
        : IDataSeedContributor, ITransientDependency
    {
        private readonly IRepository<Book, Guid> _bookRepository;

        public BookStoreDataSeederContributor(IRepository<Book, Guid> bookRepository)
        {
            _bookRepository = bookRepository;
        }

        public async Task SeedAsync(DataSeedContext context)
        {
            if (await _bookRepository.GetCountAsync() > 0)
            {
                return;
            }

            await _bookRepository.InsertAsync(
                new Book
                {
                    Name = "1984",
                    Type = BookType.Dystopia,
                    PublishDate = new DateTime(1949, 6, 8),
                    Price = 19.84f
                }
            );

            await _bookRepository.InsertAsync(
                new Book
                {
                    Name = "The Hitchhiker's Guide to the Galaxy",
                    Type = BookType.ScienceFiction,
                    PublishDate = new DateTime(1995, 9, 27),
                    Price = 42.0f
                }
            );
        }
    }
}

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.

Am I missing something here? Since abp.io seems to be a big project I suspect that the examples would not be unreasonable and hence there is must be a reason to the use of the three await statements instead of the one.

like image 480
hellouworld Avatar asked Nov 28 '19 07:11

hellouworld


1 Answers

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.

like image 157
János Pánczél Avatar answered Oct 21 '22 15:10

János Pánczél