How do you handle exceptions in async methods?
Quick Answer
Exceptions in async methods are captured and re-thrown when the returned task is awaited, so normal try/catch around the `await` works. With `Task.WhenAll`, awaiting throws only the first exception, but the task's `Exception` holds all of them as an `AggregateException`. Avoid `async void` because its exceptions can't be caught by the caller and crash the process.
Detailed Answer
Exception handling in async methods uses try-catch blocks, but with important considerations for how exceptions are propagated.
Basic exception handling:
public async Task ProcessDataAsync()
{
try
{
var data = await FetchDataAsync();
await SaveDataAsync(data);
}
catch (HttpRequestException ex)
{
// Handle network errors
_logger.LogError(ex, "Network error occurred");
throw;
}
catch (Exception ex)
{
// Handle other errors
_logger.LogError(ex, "Unexpected error");
throw;
}
finally
{
// Cleanup code always runs
_logger.LogInformation("Processing completed");
}
}
Handling exceptions with Task.WhenAll():
When using Task.WhenAll(), only the first exception is thrown. To get all exceptions:
public async Task ProcessMultipleAsync()
{
var tasks = new[]
{
ProcessItem1Async(),
ProcessItem2Async(),
ProcessItem3Async()
};
try
{
await Task.WhenAll(tasks);
}
catch (Exception ex)
{
// Only first exception is caught here
_logger.LogError(ex, "At least one task failed");
// To get ALL exceptions:
foreach (var task in tasks)
{
if (task.IsFaulted)
{
foreach (var exception in task.Exception.InnerExceptions)
{
_logger.LogError(exception, "Task exception");
}
}
}
}
}
Fire-and-forget pattern (dangerous but sometimes necessary):
// BAD: Exception will crash the application
public void StartBackgroundWork()
{
_ = DoWorkAsync(); // Fire and forget - DON'T DO THIS
}
// GOOD: Properly handled fire-and-forget
public void StartBackgroundWork()
{
_ = SafeFireAndForgetAsync(DoWorkAsync());
}
private async Task SafeFireAndForgetAsync(Task task)
{
try
{
await task;
}
catch (Exception ex)
{
_logger.LogError(ex, "Background task failed");
}
}
Exception handling with ConfigureAwait:
public async Task ProcessAsync()
{
try
{
// ConfigureAwait doesn't affect exception handling
var result = await FetchDataAsync().ConfigureAwait(false);
await SaveAsync(result).ConfigureAwait(false);
}
catch (Exception ex)
{
// Exceptions are still caught normally
_logger.LogError(ex, "Error in processing");
}
}
Key principles:
- Always await async methods to catch exceptions
- Use try-catch around await statements
- Log exceptions before rethrowing
- Be aware of AggregateException with Task.WhenAll()
- Never ignore exceptions in fire-and-forget scenarios