async / await
C# async/await is built on the Task type. async functions can await tasks without blocking a thread. Properly used, you write linear code that scales like callbacks.
Tasks, cancellation, parallel, gotchas
EXAMPLE
using System;
using System.Collections.Generic;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
// 1) The shape
async Task<string> FetchAsync(string url, CancellationToken ct)
{
using var http = new HttpClient();
var resp = await http.GetAsync(url, ct); // doesn't block the thread
resp.EnsureSuccessStatusCode();
return await resp.Content.ReadAsStringAsync(ct);
}
// 2) Call it
string body = await FetchAsync("https://api.example.com/users", CancellationToken.None);
// 3) Parallel calls — Task.WhenAll for fire-them-all-at-once
string[] urls = { "a", "b", "c" };
string[] bodies = await Task.WhenAll(urls.Select(u => FetchAsync(u, CancellationToken.None)));
// 4) First-finished
Task<string> winner = await Task.WhenAny(
FetchAsync("primary", ct),
FetchAsync("secondary", ct));
string data = await winner;
// 5) Cancellation
using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(5));
try
{
var body = await FetchAsync(url, cts.Token);
}
catch (OperationCanceledException)
{
// expected when cts expires or user cancels
}
// 6) ConfigureAwait — library code should add it
public async Task<string> LibAsync()
{
var r = await SomethingAsync().ConfigureAwait(false);
// After ConfigureAwait(false), don't touch UI state
return r;
}
// 7) Async streams (C# 8+) — IAsyncEnumerable
async IAsyncEnumerable<string> ReadLinesAsync(string path, [EnumeratorCancellation] CancellationToken ct)
{
using var sr = new System.IO.StreamReader(path);
string? line;
while ((line = await sr.ReadLineAsync(ct)) != null)
yield return line;
}
await foreach (var line in ReadLinesAsync("big.log", ct))
{
Console.WriteLine(line);
}
// 8) Common gotchas
// 8a) async void — DON'T (except event handlers)
// Exceptions escape to the SynchronizationContext and crash the process.
async void OnClick(...) // OK for UI event handlers ONLY
{
try { await DoAsync(); }
catch (Exception e) { log.Error(e); }
}
// 8b) Sync-over-async — DEADLOCK risk
var bad = SomethingAsync().Result; // BAD
var bad2 = SomethingAsync().GetAwaiter().GetResult(); // BAD on a SynchronizationContext
// Always await it, all the way up.
// 8c) Forgetting to await
FetchAsync(url); // fire-and-forget — exceptions silently swallowed!
_ = FetchAsync(url); // at least signals intent + still problematic
// 8d) Captured this in long-running tasks — beware lifetime
// 9) Background work — Task.Run for CPU-bound
int hash = await Task.Run(() => HeavyHash(data));
// Don't Task.Run for I/O — it just burns a thread
// 10) ValueTask — for hot paths where most awaits complete synchronously
async ValueTask<int> GetAsync(string key)
{
if (cache.TryGetValue(key, out var v)) return v; // no allocation
return await db.GetAsync(key); // allocates Task only when needed
}
// 11) Channels — async producer/consumer pipelines
using var ch = System.Threading.Channels.Channel.CreateBounded<string>(100);
_ = Task.Run(async () => {
await foreach (var msg in ch.Reader.ReadAllAsync()) Process(msg);
});
await ch.Writer.WriteAsync("hello");
// 12) Best practices
// • Pass CancellationToken through your async API surface
// • Use Task.WhenAll for independent parallelism
// • ConfigureAwait(false) in libraries, default (true) in apps that update UI
// • Avoid Thread.Sleep — use Task.Delay
// • One try/catch around the awaited call, not three nested ones
Why it matters
Never .Result or .Wait() on a Task — that’s how UI apps deadlock and server apps starve the thread pool. Push the async all the way up the call stack.
Tip: Tweak the snippet with Try it Yourself », then sit the quiz at the bottom of the page.
Example
Example
static async Task<string> FetchAsync(string url) {
using var client = new HttpClient();
return await client.GetStringAsync(url);
}
Try it Yourself »
Exercise
Await an async call.
string html =
client.GetStringAsync(url);
Five letters.
Discussion
Loading…