Rishi Bharadwaj
4 min readMay 7, 2024

Async/await best practices in C#

Ever since async/await was introduced in C# 5, there has been a constant confusion among developers regarding the best practices and what actually happens behind the scenes of async/await keyword.

Lets start with basics first.

Earlier in the days of windows forms, the UI lag was directly proportional to amount of time your I/O operations are taking. Which means, if you are trying to save data into database and your DB call takes 20 seconds, the user can’t do anything but wait for those 20 seconds. This was a bad user experience (anyway who cared about user experience before 2012).

Then async/await were introduced. Now we had a way to divide the work between two threads:

  1. UI thread
  2. Worker thread

The main code was run by UI thread as soon as it hits await keyword. At that moment, the UI thread get free to do something else and another thread from the thread pool will take over to do the heavy stuff.

Once we have the data available from DB, we call UI thread to execute the next step of instructions after the await keyword.

This was user can interact with some other element on UI until the data is being fetched.

So far so good?

Now lets talk about the quick best practices one by one!

  1. Never use .Wait() or .Result: It will lock the thread and you will end up using more threads to do the work. Always use await or if you need to make a synchronous call, use .GetAwaiter().GetResult().
  2. Never ever mark a void method as async — You need to see exceptions thrown by the asynchronous methods, right? If you mark your void methods as async, your inner exceptions will get swallowed and it will be a pain in (you know) to debug and find those exceptions.
  3. Always await your asyncs — There is no benefit of using async method without await keyword. It a synchronous call then.
  4. Use ValueTask return type instead of Task if 9/10 times your code gets the value from a hot path (like cache) which does not use await keyword.
  5. Use IAsyncEnumerable for streaming data. Means, if there is a situation when the collective data is huge and you need to receive the data in chunks then make use of IAsyncEnumerable.
  6. ConfigureAwait — Okay I know this one has been a nightmare for you but I’ll explain this very simply. If you don’t mark you await statements with ConfigureAwait(false) method, it’s going to call the same UI thread or worker thread (the one which lead to await statement) to execute the next set of instructions after await. Why is this bad? What if your worker thread or UI thread is busy doing something else and it gets called upon to execute next set of statements after await?

By default, the ConfigureAwait(true) value is true. You need to mark it as ConfigureAwait(false). This will tell the compiler to use any thread from threadpool to execute statements after await keyword and not particularly the one which invoked await statement.

4. Avoid return await: Don’t mark mediator class method’s with async/await if they are just returning a await method. Except in try/catch or using

Bad practice:

Good practice:

Why? : It adds additional complexity to use async/await keyword. Behind the scenes, the async method gets converted into a class by the compiler. And all we wish to do in intermediates classes is called a method that returns a task. The called upon method will already be “asyncing” and “awaiting”. We don’t need to do this at every step.

Bonus: ConfigureAwait is useful only in frameworks that make use of SynchronizationContext. Web app frameworks like ASP.Net Core do not have SynchronizationContext. So ConfigureAwait true/false is not of much use in web application. However, async/await is!

If you like my content, please leave a thumbs up!