Roadmap › Exception Handling & Custom Error Handling in .NET — Every Way, Every App Type › Module: Exceptions in C#
Lessons in this course (9)
4. Exceptions in async, parallel and background code
Intermediate★ Must knowLesson 4 of 9
csharp
// await rethrows the first inner exception directly
try { await http.GetStringAsync(url); }
catch (HttpRequestException ex) { /* handled */ }
// Task.WhenAll: await throws the FIRST exception; all are in the task's AggregateException
var all = Task.WhenAll(tasks);
try { await all; }
catch { foreach (var ex in all.Exception!.InnerExceptions) logger.LogError(ex, "Task failed"); }
// .Wait() / .Result wrap in AggregateException (and can deadlock) — avoid
// Parallel.ForEach collects exceptions into AggregateException
try { Parallel.ForEach(items, Process); }
catch (AggregateException ae) { ae.Handle(ex => ex is InvalidDataException); } // handle some, rethrow the rest
// async void: exceptions crash the process (can't be awaited/caught) — only for event handlersBackground services — an unhandled exception in ExecuteAsync stops the host by default (.NET 6+, BackgroundServiceExceptionBehavior.StopHost). Wrap each iteration:
csharp
protected override async Task ExecuteAsync(CancellationToken ct)
{
while (!ct.IsCancellationRequested)
{
try { await ProcessBatchAsync(ct); }
catch (OperationCanceledException) when (ct.IsCancellationRequested) { break; }
catch (Exception ex) { logger.LogError(ex, "Batch failed; retrying in 30 s"); }
await Task.Delay(TimeSpan.FromSeconds(30), ct);
}
}Last-chance handlers (log, then let the process die):
csharp
AppDomain.CurrentDomain.UnhandledException += (s, e) => Log.Fatal((Exception)e.ExceptionObject, "Unhandled exception");
TaskScheduler.UnobservedTaskException += (s, e) => { Log.Error(e.Exception, "Unobserved task exception"); e.SetObserved(); };