# .NET Async Best Practices: Avoiding `async void` This document defines the core rules for handling asynchronous operations in .NET, specifically focused on the dangers of `async void`. ## The Rule: NEVER Use `async void` `async void` is a critical anti-pattern in .NET that must be avoided in almost all scenarios. ### Why `async void` is Dangerous: 1. **Untraceable Crashes**: Exceptions thrown in an `async void` method cannot be caught by a `try-catch` block outside the method. They often crash the entire process. 2. **Impossible to Await**: Callers cannot know when the operation has finished, leading to race conditions and "disposed object" exceptions (especially with `DbContext`). 3. **Broken Lifecycle**: In Blazor and ASP.NET Core, the DI container might dispose of scoped services (like `DbContext`) before the `async void` method completes its work. ## Correct Patterns ### 1. Standard Methods Always return `Task` or `ValueTask` instead of `void`. ```csharp // BAD public async void SaveDataAsync() { ... } // GOOD public async Task SaveDataAsync() { ... } ``` ### 2. Async Events (Blazor/Services) When dealing with events that are defined as `Action` or `EventHandler`, do not use `async void` in the handler. Instead, refactor the event to support `Task`. #### Pattern A: Use `Func` for Events Instead of `Action`, use `Func` so the invoker can await all handlers. ```csharp // Interface event Func? OnChanged; // Invoker if (OnChanged != null) { foreach (var handler in OnChanged.GetInvocationList().Cast>()) { await handler(); } } ``` #### Pattern B: Wrapper for Legacy Events If you *must* subscribe to a legacy `void` event, use a Task-returning method and a lambda, but be aware of the "fire-and-forget" risks. ```csharp // Better than async void, but still has lifecycle risks NavigationService.OnChanged += () => { _ = HandleChangedAsync(); }; ``` ## Special Exceptions The ONLY valid place for `async void` is in **Top-level Event Handlers** in legacy UI frameworks (WinForms/WPF) where the event signature is fixed and cannot be changed. Even then, the method body should be wrapped in a `try-catch` that logs or handles all exceptions. In modern Blazor and Web development, there is **zero justification** for `async void`.