C# Async and Await
Async and await let you write code that performs long-running tasks — like downloading a file, reading a database, or calling an API — without blocking the rest of the program. Your application stays responsive while waiting for results.
The Problem: Blocking Code
┌──────────────────────────────────────────────────────────────┐ │ SYNCHRONOUS (blocking): │ │ │ │ Thread: [Task A] → [Wait 5 seconds] → [Task B] → [Task C] │ │ ↑ │ │ Everything stops here │ │ UI freezes, no other work done │ │ │ │ ASYNCHRONOUS (non-blocking): │ │ │ │ Thread: [Task A] → [Start download] → [Task B] → [Task C] │ │ ↓ │ │ download runs in background │ │ ↓ │ │ [Download done] → continue │ │ │ └──────────────────────────────────────────────────────────────┘
Key Keywords
┌────────────────────────────────────────────────────────────┐ │ async → marks a method as asynchronous │ │ await → pauses the method until the Task completes │ │ (does NOT block the thread — thread is free) │ │ Task → represents an ongoing asynchronous operation │ │ Task<T> → async operation that returns a value of type T │ └────────────────────────────────────────────────────────────┘
Basic async/await Example
using System;
using System.Threading.Tasks;
class Program
{
static async Task Main(string[] args)
{
Console.WriteLine("Start");
await DoWork(); // waits for DoWork to finish without blocking
Console.WriteLine("Done");
}
static async Task DoWork()
{
Console.WriteLine("Working...");
await Task.Delay(2000); // simulate 2-second delay (non-blocking)
Console.WriteLine("Work complete.");
}
}
// Output (with 2-second pause before "Work complete."):
// Start
// Working...
// Work complete.
// Done
Returning a Value from Async Method
Use Task<T> as the return type when your async method produces a result.
static async Task<int> GetScoreAsync()
{
await Task.Delay(1000); // simulate fetching from DB
return 95;
}
static async Task Main()
{
Console.WriteLine("Fetching score...");
int score = await GetScoreAsync();
Console.WriteLine("Score: " + score); // Score: 95
}
Task Return Types
┌──────────────────────────────────────────────────────────────┐ │ Return Type Meaning │ ├──────────────────────────────────────────────────────────────┤ │ Task async method — no return value │ │ Task<int> async method — returns int │ │ Task<string> async method — returns string │ │ ValueTask lightweight version of Task │ │ void async event handler only (avoid elsewhere) │ └──────────────────────────────────────────────────────────────┘
Calling an API Asynchronously
using System;
using System.Net.Http;
using System.Threading.Tasks;
class Program
{
static async Task Main()
{
HttpClient client = new HttpClient();
Console.WriteLine("Fetching data...");
string content = await client.GetStringAsync("https://api.github.com");
Console.WriteLine("Received " + content.Length + " characters.");
client.Dispose();
}
}
Running Multiple Tasks in Parallel
Use Task.WhenAll() to start several async operations at once and wait for all of them to finish.
static async Task DownloadFile(string name, int delay)
{
Console.WriteLine("Starting: " + name);
await Task.Delay(delay);
Console.WriteLine("Done: " + name);
}
static async Task Main()
{
// Sequential (slow — waits for each in turn):
// await DownloadFile("A", 2000);
// await DownloadFile("B", 1500);
// Total time: 3500ms
// Parallel (fast — all run at the same time):
await Task.WhenAll(
DownloadFile("A", 2000),
DownloadFile("B", 1500),
DownloadFile("C", 1000)
);
// Total time: ~2000ms (as long as the longest task)
Console.WriteLine("All downloads complete.");
}
WhenAll vs Sequential Diagram
┌──────────────────────────────────────────────────────────────┐ │ SEQUENTIAL (await each): │ │ [File A 2s] → [File B 1.5s] → [File C 1s] Total: 4.5s │ │ │ │ PARALLEL (Task.WhenAll): │ │ [File A 2s ──────────────────] │ │ [File B 1.5s ───────────] │ │ [File C 1s ──────] Total: 2s (longest) │ └──────────────────────────────────────────────────────────────┘
Task.WhenAny — First to Finish Wins
static async Task<string> FetchFromServerA()
{
await Task.Delay(3000);
return "Data from Server A";
}
static async Task<string> FetchFromServerB()
{
await Task.Delay(1000);
return "Data from Server B";
}
static async Task Main()
{
Task<string> taskA = FetchFromServerA();
Task<string> taskB = FetchFromServerB();
Task<string> winner = await Task.WhenAny(taskA, taskB);
Console.WriteLine(await winner); // Data from Server B (finished first)
}
Exception Handling in Async Code
static async Task<string> FetchData(string url)
{
HttpClient client = new HttpClient();
return await client.GetStringAsync(url);
}
static async Task Main()
{
try
{
string data = await FetchData("https://invalid-url-xyz.com");
Console.WriteLine(data);
}
catch (HttpRequestException ex)
{
Console.WriteLine("Network error: " + ex.Message);
}
catch (Exception ex)
{
Console.WriteLine("Error: " + ex.Message);
}
}
async/await Flow Diagram
┌──────────────────────────────────────────────────────────────┐
│ static async Task Main() │
│ { │
│ Console.Write("Start"); ← executes │
│ await DoWork(); ← pauses here │
│ Console.Write("Done"); ← resumes after DoWork done │
│ } │
│ │
│ Flow: │
│ Main starts → prints "Start" → hits await │
│ → DoWork begins → thread returns to caller │
│ → DoWork finishes → Main resumes → prints "Done" │
└──────────────────────────────────────────────────────────────┘
Common Mistakes to Avoid
// ❌ DO NOT do this (blocks the thread):
string result = FetchData().Result; // deadlock risk
string result = FetchData().GetAwaiter().GetResult(); // blocks thread
// ✅ Always await:
string result = await FetchData();
// ❌ Avoid async void (cannot be awaited, errors get lost):
async void LoadData() { ... }
// ✅ Use async Task instead:
async Task LoadData() { ... }
Quick Summary
┌──────────────────────────────────────────────────────────────┐ │ async → marks a method as async │ │ await → yields control until Task is done │ │ Task → async method with no return value │ │ Task<T> → async method returning T │ │ Task.Delay → non-blocking pause (like Thread.Sleep but async│ │ WhenAll → await multiple tasks simultaneously │ │ WhenAny → await whichever task finishes first │ │ │ │ Always use try/catch around awaited calls │ │ Never block with .Result or .Wait() in async code │ └──────────────────────────────────────────────────────────────┘
Async/await is the modern standard for handling any operation that takes time — network calls, file I/O, database queries. It keeps your application fast and responsive without the complexity of manual threading. Every serious C# application relies on it.
