C# · Intermediate

Async and Await

Waiting for a database or a web service takes time. async and await let your program wait without blocking — so a web server can serve other users in the meantime.

Advanced 6 min Lesson 22 of 26Examples target C# 13 on .NET 9 unless stated otherwise.
0/26 in C#
On this page · 6 min · Advanced

In simple words#

You order food at a counter and get a token. Instead of standing still, you sit and chat until your number is called. await is “wait for my token without blocking everyone else”.

async
Marks a method that can await.
await
Pause here until the task finishes, freeing the thread.
Task
A promise of a result that will come later.

Learning objectives#

  • Write async methods that return Task or Task<T>
  • Await results instead of blocking
  • Run independent calls in parallel with Task.WhenAll
  • Catch exceptions from async methods

Explanation#

An async method returns Task (no result) or Task<T> (a result of type T). By convention its name ends in Async: GetUserAsync.

await pauses the method until the task finishes, then continues with the result. While it waits, the thread is free to do other work — that is the whole point.

Awaiting calls one after another runs them in sequence. If calls do not depend on each other, start them all first and await Task.WhenAll to run them at the same time.

Exceptions thrown inside an async method are stored in the Task and re-thrown when you await it, so a normal try/catch around await works.

Never block on async code with .Result or .Wait() in ASP.NET — it wastes threads and can deadlock. Make the caller async too (“async all the way”).

Syntax#

C# Illustrative
async Task<User> GetUserAsync(int id)
{
    var user = await db.Users.FindAsync(id);
    return user;
}


var user = await GetUserAsync(1);
await Task.WhenAll(taskA, taskB);

Code example: Sequential, parallel and failing calls#

C#Program.cs Illustrative
using System.Diagnostics;


string user = await GetUserAsync(1);
Console.WriteLine(user);


var timer = Stopwatch.StartNew();
Task<int> ordersTask = GetOrderCountAsync(1);   // both start now
Task<decimal> balanceTask = GetBalanceAsync(1);
await Task.WhenAll(ordersTask, balanceTask);
Console.WriteLine(
quot;Orders:
{await ordersTask}, balance: {await balanceTask}");
Console.WriteLine(
quot;Ran in parallel:
{timer.ElapsedMilliseconds < 900}");
try { await GetUserAsync(-1); } catch (ArgumentException ex) { Console.WriteLine(
quot;Caught:
{ex.Message}");
} static async Task<string> GetUserAsync(int id) { await Task.Delay(300); // pretend this is a database call if (id < 0) throw new ArgumentException("Invalid id"); return
quot;User
{id}: Asha";
} static async Task<int> GetOrderCountAsync(int userId) { await Task.Delay(500); return 7; } static async Task<decimal> GetBalanceAsync(int userId) { await Task.Delay(500); return 2500m; }
Expected output
User 1: Asha
Orders: 7, balance: 2500
Ran in parallel: True
Caught: Invalid id

This example is illustrative. Copy or download it and run it in your own environment; the expected output is shown above.

Step-by-step explanation#

  1. await GetUserAsync(1) waits about 300 ms without blocking a thread, then gives back the string.
  2. GetOrderCountAsync and GetBalanceAsync are started before either is awaited, so their 500 ms delays overlap.
  3. Task.WhenAll finishes when both are done — about 500 ms in total instead of 1000 ms.
  4. Awaiting an already-finished task just returns its result immediately.
  5. The exception from GetUserAsync(-1) is re-thrown at the await and caught normally.

Real-world use case#

An ASP.NET Core product page endpoint awaits the product from SQL Server, and in parallel awaits reviews and stock level from two other services, returning the page in the time of the slowest call instead of the sum of all three.

Common mistakes#

Blocking with .Result or .Wait()

This ties up a thread and can deadlock in some apps. Use await, and make the calling method async.

async void

An async void method cannot be awaited and its exceptions crash the process. Return Task. (Only UI event handlers use async void.)

Awaiting independent calls one by one

await A(); await B(); takes A + B. Start both, then await Task.WhenAll for max(A, B).

Best practices#

  • Go async all the way from controller to database
  • Name async methods with the Async suffix
  • Pass CancellationToken through long operations
  • Use Task.WhenAll for independent work

Remember this#

async method → returns Task or Task<T>.

await = “wait here without blocking, then continue”.

Never .Result in web apps.

Practice exercise#

Your task: Write an async method DownloadAsync(string name, int ms) that waits ms milliseconds and returns "<name> done". Start three downloads together and print all results with Task.WhenAll.

Need a hint?
Task.WhenAll on Task<string> returns a string[] when awaited.
Show solution
C# Illustrative
string[] results = await Task.WhenAll(
    DownloadAsync("a.pdf", 300),
    DownloadAsync("b.pdf", 200),
    DownloadAsync("c.pdf", 100));
Console.WriteLine(string.Join(", ", results));   // a.pdf done, b.pdf done, c.pdf done


static async Task<string> DownloadAsync(string name, int ms)
{
    await Task.Delay(ms);
    return 
quot;
{name} done";
}

Mini quiz#

Async and Await quizMultiple choice Question 1 of 3

What should an async method that produces an int return?

Score so far: 0/0

Want more? Practise all C# MCQs · C# interview questions

My notes#

Saved in this browser.

Progress is saved in this browser. Sign in to sync.