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 (likevoid)Task<T>— completes with a value of typeT
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#
A method that uses
awaitmust be markedasync.An
asyncmethod returnsTask,Task<T>, orvoid(avoidasync voidexcept for event handlers).awaitcan only appear inside anasyncmethod.asyncdoes 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
Asyncvariant, use it — and make your own methodasync Task<T>in turn.
14.5.8. Practice#
Write
async Task<string> ReadFirstLineAsync(string path)that reads only the first line of a file asynchronously.Use
Task.WhenAllto fetch three URLs simultaneously and print the length of each response.Write
async Task<int> CountWordsAsync(string path)that reads a text file and returns the word count.
Footnotes