Syntax
async Task MethodName() { } // not async voidExamples
async void vs async Task
async void methods can't be awaited, and exceptions inside them can crash the whole application unpredictably.
// Avoid: exceptions here cannot be caught by the caller
async void RiskyOperation()
{
throw new InvalidOperationException("This will crash unpredictably");
}
// Prefer: exceptions surface normally when awaited
async Task SaferOperationAsync()
{
throw new InvalidOperationException("This can be caught properly");
}
try
{
await SaferOperationAsync();
}
catch (InvalidOperationException e)
{
Console.WriteLine($"Caught: {e.Message}");
}The Deadlock Risk of Blocking on Async Code
Calling .Result or .Wait() synchronously on an async method can deadlock in certain application contexts.
// Risky in UI/ASP.NET (classic) contexts - can deadlock
// int result = SomeAsyncMethod().Result;
// Safe: await it properly, all the way up the call chain
async Task<int> GetResultSafelyAsync()
{
return await SomeAsyncMethod();
}
async Task<int> SomeAsyncMethod()
{
await Task.Delay(100);
return 42;
}ConfigureAwait(false) in Library Code
In library code, ConfigureAwait(false) avoids capturing the original context, which can improve performance and avoid deadlocks.
public async Task<string> LoadDataAsync()
{
// In library code (not top-level app code), this avoids resuming
// on the original context unnecessarily
await Task.Delay(500).ConfigureAwait(false);
return "data";
}Best practices
- Avoid async void entirely except for top-level UI event handlers, which are required to return void by their delegate signature
- Never call .Result or .Wait() on a Task from synchronous code that has a synchronization context - await all the way up the call chain instead
- Use ConfigureAwait(false) in reusable library code that does not need to resume on a specific UI or request context, but skip it in top-level application/UI code where the context often matters
- Wrap awaited calls in try/catch to handle exceptions properly - unlike async void, an awaited async Task lets exceptions propagate normally to the caller
At a glance
- Purpose
- Applications on the .NET platform
- File extension
- .cs
- Runs in
- .NET runtime
- Usually used with
- .NET SDK and libraries
Specifications & further reading
Related C# documentation
The async and await keywords let you write asynchronous code that reads almost like ordinary sequential code. Marking a method async allows it to use await, which pauses execution at that point until the awaited operation completes, without blocking the calling thread - freeing it up to do other work in the meantime. This is essential for keeping applications responsive during I/O-bound work like network calls, file access, or database queries.Task & Task<T>
Task represents an asynchronous operation that may still be running, and Task<T> represents one that will eventually produce a value of type T. Tasks are the building block that async/await is built on top of - an async method actually returns a Task (or Task<T>) that the caller can await, check the status of, or attach continuations to.Task.WhenAll & Task.WhenAny
Task.WhenAll() lets you run multiple independent asynchronous operations concurrently and await all of them together, completing once every task finishes - much faster than awaiting them one at a time. Task.WhenAny() completes as soon as the first of several tasks finishes, useful for timeout patterns or taking whichever result arrives first.CancellationToken
CancellationToken provides a standard, cooperative way to signal that an asynchronous operation should stop before it completes naturally. A CancellationTokenSource creates the token and controls when cancellation is requested; the token itself is passed into async methods, which check it periodically (or pass it to another cancellable operation) and can throw an OperationCanceledException to unwind cleanly.