Read the syntax
Prefer async Task or async Task<T> for awaitable operations. async void is intended mainly for event handlers because callers cannot await it or reliably observe its exceptions.
Avoid blocking on .Result or .Wait in code with a synchronization context; use await through the call chain. ConfigureAwait(false) affects continuation-context capture, not whether work is asynchronous.
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}");
}- async void methods can't be awaited, and exceptions inside them can crash the whole application unpredictably.
- Prefer async Task or async Task<T> for awaitable operations. async void is intended mainly for event handlers because callers cannot await it or reliably observe its exceptions.
- RiskyOperation is declared but never called. Awaiting SaferOperationAsync propagates its exception to the surrounding catch, which prints the 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;
}- Calling .Result or .Wait() synchronously on an async method can deadlock in certain application contexts.
- Prefer async Task or async Task<T> for awaitable operations. async void is intended mainly for event handlers because callers cannot await it or reliably observe its exceptions.
- The blocking call is commented out. This snippet only declares GetResultSafelyAsync and SomeAsyncMethod; a caller must await GetResultSafelyAsync to receive 42.
ConfigureAwait(false) in Library Code
In library code, ConfigureAwait(false) avoids capturing the original context, which can improve performance and avoid deadlocks.
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";
}
Console.WriteLine(await LoadDataAsync());- In library code, ConfigureAwait(false) avoids capturing the original context, which can improve performance and avoid deadlocks.
- Prefer async Task or async Task<T> for awaitable operations. async void is intended mainly for event handlers because callers cannot await it or reliably observe its exceptions.
- LoadDataAsync awaits a delay without requesting return to a captured synchronization context, then returns data. The top-level caller awaits it and prints 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
Runtime & versions
.NET runtime. Feature availability depends on the language and runtime version used by your project; consult the official reference below.
C# language reference