# .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<Task>` for Events
Instead of `Action`, use `Func<Task>` so the invoker can await all handlers.
```csharp
// Interface
event Func<Task>? OnChanged;

// Invoker
if (OnChanged != null)
{
    foreach (var handler in OnChanged.GetInvocationList().Cast<Func<Task>>())
    {
        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`.
