All modern .NET I/O (HTTP, databases, files) is async; servers scale only if you use it correctly.
A Task represents work that completes in the future; Task<T> produces a value. An async method can await tasks: execution pauses at the await without blocking the thread, and resumes when the task completes. The method itself returns a Task/Task<T> to its caller immediately.
async Task<string> LoadAsync(string url) => await http.GetStringAsync(url);Rules of thumb:
- Name async methods with an
Asyncsuffix and returnTask,Task<T>orValueTask<T>. Useasync voidonly for event handlers: exceptions from it can't be awaited and crash the process. - Go async all the way. Blocking on a task with
.Resultor.Wait()wastes a thread and can deadlock in UI apps. - To run independent operations concurrently, start them first and then await them together:
await Task.WhenAll(t1, t2). Awaiting one after another runs them sequentially. Task.Delayis the async version ofThread.Sleep.- Exceptions thrown inside an async method are stored in the task and rethrown at the
await. - Pass a
CancellationTokenthrough to support cancellation (Advanced lesson).
Async is about not blocking threads while waiting for I/O; it doesn't make CPU-bound work faster. For CPU-heavy work, Task.Run moves it to the thread pool.
Top-level programs and static async Task Main can use await directly.
Example
using System.Diagnostics;
static async Task<int> FetchAsync(string name, int ms)
{
await Task.Delay(ms); // simulates I/O, no thread blocked
Console.WriteLine($"{name} done");
return ms;
}
var sw = Stopwatch.StartNew();
int a = await FetchAsync("A", 300); // sequential
int b = await FetchAsync("B", 300);
Console.WriteLine($"sequential {sw.ElapsedMilliseconds} ms"); // at least 600 ms
sw.Restart();
Task<int> t1 = FetchAsync("C", 300); // start both...
Task<int> t2 = FetchAsync("D", 300);
int[] results = await Task.WhenAll(t1, t2); // ...then await together
Console.WriteLine($"concurrent {sw.ElapsedMilliseconds} ms, sum {results.Sum()}");
// concurrent about 300 ms, sum 600
try { await Task.FromException(new InvalidOperationException("boom")); }
catch (InvalidOperationException ex) { Console.WriteLine(ex.Message); } // boomRemember
- await pauses the method without blocking the thread
- Start tasks first, then await Task.WhenAll for concurrency
- Avoid .Result/.Wait() and async void
- Exceptions surface at the await
Watch out for
- Calling an async method without awaiting it, so errors are lost and work may not finish.