Files
Nexus.Reader/.agents/skills/dotnet-async-void.md
T

2.3 KiB

.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.

// 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.

// 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.

// 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.