14.5. Async and Await#

Modern C# programs constantly do things that take time: read files, call APIs, query databases. async/await lets your program stay responsive during waits instead of blocking the thread.

14.5.1. The Problem: Blocking#

A synchronous file read blocks until the OS returns data. Nothing else can run on that thread meanwhile.

// Blocking — thread is frozen during the read
string text = File.ReadAllText("data.txt");
Console.WriteLine(text);

In a UI app or server this is a problem — the app freezes, or throughput collapses under load. The fix is asynchronous I/O.

14.5.2. Task — a Promise of a Future Value#

Task represents an operation that will complete in the future:

  • Task — completes, no return value (like void)

  • Task<T> — completes with a value of type T

You don’t manually create Task objects — you await methods that return them.

14.5.3. async and await#

using System.Net.Http;

// async marks the method as asynchronous
static async Task<string> FetchAsync(string url)
{
    using var client = new HttpClient();
    // await suspends THIS method until the HTTP call finishes
    // — the thread is free to do other work meanwhile
    string content = await client.GetStringAsync(url);
    return content[..200];  // first 200 chars
}

string result = await FetchAsync("https://example.com");
Console.WriteLine(result);

14.5.4. Async File I/O#

All major I/O APIs have async counterparts. Prefer them in any method that already has async:

// Write a file asynchronously
await File.WriteAllTextAsync("out.txt", "hello async");

// Read it back
string text = await File.ReadAllTextAsync("out.txt");
Console.WriteLine(text);  // hello async

14.5.5. Rules of async/await#

  1. A method that uses await must be marked async.

  2. An async method returns Task, Task<T>, or void (avoid async void except for event handlers).

  3. await can only appear inside an async method.

  4. async does not mean the method runs on a background thread — it means it can yield its thread while waiting for I/O.

14.5.6. Running Multiple Tasks in Parallel#

Use Task.WhenAll to start several async operations and wait for all of them:

static async Task<int> SlowAdd(int a, int b)
{
    await Task.Delay(100);  // simulate slow work
    return a + b;
}

// Start both without awaiting immediately
var t1 = SlowAdd(1, 2);
var t2 = SlowAdd(3, 4);

// Await both together — total ~100 ms, not 200 ms
int[] results = await Task.WhenAll(t1, t2);
Console.WriteLine(string.Join(", ", results));  // 3, 7

14.5.7. When to Use Async#

Good fit

Not needed

Network calls (HTTP, gRPC)

Pure CPU computation

File or database I/O

In-memory data manipulation

Any operation that waits on an external resource

Short, synchronous helpers

Rule of thumb: if the underlying API has an Async variant, use it — and make your own method async Task<T> in turn.

14.5.8. Practice#

  1. Write async Task<string> ReadFirstLineAsync(string path) that reads only the first line of a file asynchronously.

  2. Use Task.WhenAll to fetch three URLs simultaneously and print the length of each response.

  3. Write async Task<int> CountWordsAsync(string path) that reads a text file and returns the word count.

Footnotes