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.

Leave a Comment

Your email address will not be published. Required fields are marked *