C# Async and Await
Keep a program responsive while waiting for I/O, understand Task as an eventual result, and avoid blocking, lost exceptions, and fake parallelism.
Before this lesson
- Methods and return values
- Exceptions
- Files
- Generics
Explain Task and await without a thread myth
Write an async method and propagate asynchrony to its caller
Recognize blocking and fire-and-forget hazards
The short answer
An async method can pause at await while an incomplete Task finishes, allowing its thread to do other work. It resumes later and produces a Task or Task<T>; async does not automatically create a new thread or make CPU work faster.
Waiting and computing are different work
Reading a remote response or file often spends most of its time waiting for another system. Blocking a server thread or UI thread during that wait wastes capacity or freezes interaction. Asynchronous APIs represent the unfinished operation so the caller can yield control and resume when completion arrives.
CPU-bound work actively uses the processor. Marking a heavy calculation async does not make it faster. Parallel execution and background scheduling are separate decisions with costs and thread-safety concerns.
| Work | Main constraint | Typical strategy |
|---|---|---|
| File/network/database I/O | Waiting | Async APIs and await |
| Large calculation | CPU time | Optimize; consider measured parallelism |
| Tiny quick operation | Neither | Keep synchronous |
Task represents eventual completion
Task means an operation will finish later without a returned value; Task<T> will eventually produce T. Calling an async method starts it and returns its task. await checks that task: if incomplete, the method returns control to its caller; after completion, execution continues after the await.
The starter uses GetAwaiter().GetResult only as an adapter for the course's older console compiler entry point. In a current .NET console project, prefer static async Task Main() and await PrepareReportAsync() so asynchrony flows naturally rather than blocking at the top.
// Preferred in a current local .NET console project
static async Task Main()
{
await PrepareReportAsync();
}Async code should usually continue upward
When a method calls an asynchronous API, it should usually become async and return a Task to its caller. Blocking with .Result or .Wait can waste threads and can deadlock in environments with a synchronization context. ‘Async all the way’ keeps cancellation and failure visible.
Avoid async void except for event-handler signatures. A caller cannot await its completion or catch its later exceptions normally. Returning Task makes completion part of the method contract.
static async Task<string> LoadMessageAsync(string path)
{
string text = await File.ReadAllTextAsync(path);
return text.Trim();
}
// Caller:
string message = await LoadMessageAsync("message.txt");Concurrency is useful only when operations are independent
Starting two independent tasks before awaiting them can overlap their waiting time. Task.WhenAll waits for both. Do not parallelize operations that must occur in order or that mutate shared state without coordination.
Exceptions from awaited tasks are rethrown at the await and can be handled with normal try/catch. Cancellation is cooperative through CancellationToken: callers signal that results are no longer needed, and operations must observe the token. Timeouts, retries, and cancellation are related but distinct policies.
Task first = Task.Delay(300);
Task second = Task.Delay(500);
await Task.WhenAll(first, second);
Console.WriteLine("Both completed");Good habits
- Name asynchronous methods with an Async suffix.
- Return Task, not void, unless implementing an event handler.
- Do not wrap naturally asynchronous I/O in Task.Run.
- Pass cancellation through layers that can honor it.
- Limit concurrency when processing an unbounded number of items.
Quick knowledge check
Answer before you reveal.
01Does async automatically run a method on another thread?
No. It enables non-blocking composition around Tasks; thread use depends on the operation and environment.
02Why is async void difficult outside event handlers?
Callers cannot await it, compose its completion, or observe exceptions through a returned Task.
Practice challenge
Now build it without copying.
Create two async methods that simulate loading profile and order data with different Task.Delay durations. Start both, await Task.WhenAll, and print a combined report only after both finish.
You are done when
- Both operations are started before the combined await
- Methods return Task<T> rather than async void
- The final report uses both returned values
Stretch: Run the operations sequentially and concurrently, measure elapsed time with Stopwatch, and explain why the concurrent version approaches the longer delay rather than their sum.
Open challenge in playgroundLesson checkpoint
One small step locks it in
Mark this lesson complete, then keep the momentum going.
Clear up the details
Frequently asked questions
What is the difference between concurrency and parallelism?
Concurrency means multiple operations are in progress with overlapping lifetimes. Parallelism means work executes at the same instant on multiple processing resources.
Should every method be async?
No. Use async when composing asynchronous operations. Pure calculations and quick synchronous work should remain synchronous.
When should I use ConfigureAwait(false)?
It is mainly a library-context concern. Application code should first follow its framework guidance; do not add it mechanically without understanding synchronization contexts.