Skip to content

Intermediate · Exceptions, delegates, events and async · topic 29 of 43 · 35 min

async and await: waiting without blocking

Explanation

Calls to the network, disk or a database spend most of their time waiting. async and await let your code wait without blocking a thread: the method pauses at await, the thread goes off to do other work, and the method continues when the result arrives. A Task represents work that will finish later (Task<T> also gives a result). Start several tasks and await Task.WhenAll to run them at the same time, and avoid async void and .Result.

The problem: waiting wastes threads

A thread is a worker that runs your code. Calling a payment gateway might take 800 milliseconds, and almost all of it is just waiting for the reply. If the thread sits blocked during that wait, it cannot do anything else. In a desktop app the window freezes. In a web server, each blocked thread is one fewer request it can serve, so under load the site slows down or fails.

Think of it like this: Think of a waiter in a busy dhaba. A blocking waiter takes your order, then stands in the kitchen until your food is ready, ignoring every other table. An async waiter gives the order to the kitchen, serves other tables, and comes back when the kitchen rings the bell. Same one waiter, many more customers served.

Task and Task<T>

A Task is a promise of work that will complete in the future. Task<T> also promises a result of type T. You do not usually create them by hand; .NET methods that do I/O return them, and their names end in Async: File.ReadAllTextAsync, HttpClient.GetStringAsync, SaveChangesAsync.

Writing an async method

csharp
static async Task<string> GetGreetingAsync(string name)
{
    await Task.Delay(1000);          // pretend this is a slow network call
    return $"Namaste, {name}";
}

string message = await GetGreetingAsync("Asha");   // top-level await works in Program.cs
Console.WriteLine(message);
async allows await inside the method. It returns Task<string>, but you return a plain string; the compiler wraps it. await unwraps the result.

What await really does

  1. The method runs normally until it reaches an await on something that is not finished yet.
  2. It pauses there and returns an unfinished Task to its caller. The thread is free to do other work.
  3. When the awaited operation finishes, the rest of the method continues (possibly on another thread).
  4. When the method returns, its Task completes, and whoever awaited it continues.
csharp
Console.WriteLine("1");
Task work = SlowAsync();
Console.WriteLine("2");
await work;
Console.WriteLine("4");

static async Task SlowAsync()
{
    await Task.Delay(500);
    Console.WriteLine("3");
}
Shows 1, 2, 3, 4. SlowAsync starts, pauses at its await and hands back a Task, so 2 is printed before 3.

Doing several things at once: Task.WhenAll

csharp
using System.Diagnostics;

var watch = Stopwatch.StartNew();
Task<decimal> fare = GetFareAsync();        // starts now
Task<int> seats = GetSeatsAsync();          // also starts now
await Task.WhenAll(fare, seats);            // wait for both
Console.WriteLine($"Fare {fare.Result}, seats {seats.Result}, took about {watch.ElapsedMilliseconds} ms");

static async Task<decimal> GetFareAsync() { await Task.Delay(2000); return 1450m; }
static async Task<int> GetSeatsAsync() { await Task.Delay(1500); return 23; }
Both waits overlap, so it takes about 2 seconds, not 3.5. After WhenAll, reading .Result is safe because the tasks are already finished.

A real example: calling a web API

csharp
using var http = new HttpClient();
http.Timeout = TimeSpan.FromSeconds(10);
string body = await http.GetStringAsync("https://api.github.com/zen",
    CancellationToken.None);
In real apps, create HttpClient once and reuse it (in ASP.NET Core, use IHttpClientFactory); creating a new one per request can exhaust network sockets. GitHub's API may also require a User-Agent header.

The rules that prevent most async bugs

DoAvoidWhy
async Task / async Task<T>async voidExceptions from async void cannot be caught by the caller and can crash the app. Only UI event handlers should be async void.
await tasktask.Result or task.Wait()Blocking on async code wastes a thread and can deadlock in UI apps and older ASP.NET.
await every Task you startCalling an async method without awaitThe work runs unobserved; errors vanish. The compiler warns (CS4014).
Async all the way upMixing sync and async halfwayOnce a method awaits, its callers should be async too.
Name methods ...AsyncUnclear namesCallers know they must await.

I/O-bound versus CPU-bound

async helps with I/O-bound work: waiting for network, disk or database. It does not make calculations faster. For heavy CPU work, like resizing 500 photos, the work itself needs a thread; in a desktop app you can move it off the UI thread with await Task.Run(() => Resize(photos)). In ASP.NET Core, avoid wrapping CPU work in Task.Run just to look async.

Exceptions and cancellation

If an awaited task fails, await rethrows its exception, so a normal try/catch around await works. Long operations usually accept a CancellationToken so they can be stopped, for example when a user leaves the page.

Need help?

Stuck on a word or an idea? Search this course, or every course, and jump straight to the explanation or a worked question.

Try:

Watch the lesson

async and await: waiting without blocking · 1/7

async and await

In this lesson your code will wait for slow things without blocking.

Prefer a video by a teacher? Find free videos on “C# async await Task explained for beginners Task.WhenAll” on YouTube ↗

In real life: A travel app loading a trip page

When you open a trip on a travel app, it fetches the train status, the weather at the destination and the hotel booking. Done one after another, each wait adds up. Started together with Task.WhenAll, the page loads in roughly the time of the slowest call.

csharp
var status = GetTrainStatusAsync("12951");
var weather = GetWeatherAsync("Mumbai");
var hotel = GetHotelAsync(bookingId: 88231);

await Task.WhenAll(status, weather, hotel);
Console.WriteLine($"{await status} | {await weather} | {await hotel}");

static async Task<string> GetTrainStatusAsync(string trainNo) { await Task.Delay(900); return $"{trainNo}: on time"; }
static async Task<string> GetWeatherAsync(string city) { await Task.Delay(600); return $"{city}: 31 C"; }
static async Task<string> GetHotelAsync(int bookingId) { await Task.Delay(700); return $"Booking {bookingId}: confirmed"; }
Takes about 0.9 seconds instead of 2.2. Awaiting an already-finished task just returns its result. The services here are simulated with Task.Delay.

Practice (0/4 done)

Task 1 · Question

What should an async method that returns no value usually return?

Task 2 · Question

In what order are the letters printed? Console.Write("A"); var t = WorkAsync(); Console.Write("B"); await t; Console.Write("D"); static async Task WorkAsync() { await Task.Delay(100); Console.Write("C"); }

Task 3 · Question

Two independent calls take 2 s and 3 s. Started together and awaited with Task.WhenAll, about how long does it take?

Task 4 · Practice

Write an async method GetPriceAsync(string item) that waits a different time per item with Task.Delay and returns a price. Fetch prices for "rice", "dal" and "oil" first one by one, then all together with Task.WhenAll, and print how long each approach took using Stopwatch.

Quick quiz

Question 1

1.What kind of work benefits most from async/await?

Question 2

2.Why avoid task.Result on an unfinished task?

Question 3

3.When is async void acceptable?

Question 4

4.What does await do if the awaited task throws?

Question 5

5.What is the convention for naming methods that return a Task?

Read more (free)

Open a question to see the answer and why. The first ones are this topic's own quiz, so try the quiz above before you peek.

What kind of work benefits most from async/await?

Answer: Waiting for network, disk or database (I/O)

async frees threads during I/O waits.

Why avoid task.Result on an unfinished task?

Answer: It blocks the thread and can deadlock in some apps

Blocking defeats the purpose of async and can deadlock.

When is async void acceptable?

Answer: For UI event handlers

Event handlers must return void; everywhere else return Task.

What does await do if the awaited task throws?

Answer: Rethrows the exception so try/catch can handle it

Exceptions flow through await normally.

What is the convention for naming methods that return a Task?

Answer: End with Async

GetDataAsync tells the caller to await it.

What is the danger of a long CPU loop inside an async task?Related

Answer: It blocks the worker thread, so other tasks stall

Tasks must yield at .await points; use spawn_blocking for heavy work.

From Async Rust with tokio

Does await block the whole Dart program?Related

Answer: No, it pauses only the current function while the event loop keeps working

Other events continue.

From Futures and async/await

Two independent requests each take 1 s. With await one after the other, the total is about…Related

Answer: 2 s

Sequential awaits add up; Promise.all would take about 1 s.

From async and await

Three independent requests each take about 1 second. How long does await Promise.all([a(), b(), c()]) take?Related

Answer: About 1 second

Promise.all starts them together, so the total is about the slowest one. Awaiting them one by one would take about 3 seconds.

From async/await and error handling

Pass the quiz (60% or more) or mark this topic as done when you are ready.

Your progress is saved on this device only.

Next steps

  1. 1 more topic in “Exceptions, delegates, events and async” before its exam: Files and JSON with System.Text.Json.
  2. Module 7 exam: Exceptions, delegates, events and async · 14 questions · 25 min, without help. 5 topics of this module still to finish first.
  3. Then the next module, Web APIs with ASP.NET Core and EF Core. 43 topics left in the course before the final exam.

← Previous: Events: letting objects notify each other