async void
event handlers
C#
programming best practices
asynchronous programming

Should I avoid 'async void' event handlers?

Interview Questions practice on Codemia

Over 8,000 real interview questions from top companies, searchable by company and role.

Browse interview questions

Introduction

async void is usually discouraged in C sharp because it breaks normal task based composition and exception handling. Event handlers are the one common exception, since framework delegate signatures typically require void. The right rule is not absolute avoidance, but strict containment: use async void only at event boundaries and keep real work in Task methods.

Why async void Is Different

Methods returning Task can be awaited, chained, cancelled, and tested with standard async tools. async void methods cannot be awaited by callers, which means completion and failure cannot be observed in the same way.

Consequences include:

  • harder error propagation
  • reduced testability
  • unclear lifecycle for calling code

Example of risky non event use:

csharp
1public async void ProcessOrderAsync()
2{
3    await Task.Delay(100);
4    throw new InvalidOperationException("Failure");
5}

A caller cannot await this or catch exceptions through normal await flow.

Event Handlers Are a Valid Exception

UI frameworks define event delegates that return void, so event handlers often must be async void.

csharp
1private async void SaveButton_Click(object sender, EventArgs e)
2{
3    try
4    {
5        await SaveAsync();
6        statusLabel.Text = "Saved";
7    }
8    catch (Exception ex)
9    {
10        statusLabel.Text = ex.Message;
11    }
12}

The handler is acceptable because signature is fixed by framework contract.

Keep Handlers Thin, Move Logic to Task

To limit risk, keep only orchestration in the handler and move business logic to task returning methods.

csharp
1private async void RefreshButton_Click(object sender, EventArgs e)
2{
3    await RefreshAsync();
4}
5
6public async Task RefreshAsync()
7{
8    var data = await apiClient.LoadAsync();
9    Render(data);
10}

Now core logic is reusable and testable.

Exception Handling Guidance

Always add local try and catch in async void event handlers. Unhandled exceptions in these handlers can crash UI contexts or surface through global exception handlers with limited context.

Pattern:

  • catch known transient exceptions and show user message
  • log unexpected exceptions with action metadata
  • keep global handler as safety net, not primary handling
csharp
1private async void Upload_Click(object sender, EventArgs e)
2{
3    try
4    {
5        await uploader.RunAsync();
6    }
7    catch (TimeoutException ex)
8    {
9        logger.LogWarning(ex, "Upload timeout");
10        ShowError("Upload timed out. Try again.");
11    }
12    catch (Exception ex)
13    {
14        logger.LogError(ex, "Upload failed");
15        ShowError("Unexpected upload failure.");
16    }
17}

Reentrancy and Double Click Protection

Event handlers can be triggered repeatedly before prior async work finishes. Guard against overlap.

csharp
1private bool _isBusy;
2
3private async void RunButton_Click(object sender, EventArgs e)
4{
5    if (_isBusy) return;
6    _isBusy = true;
7
8    try
9    {
10        await RunAsync();
11    }
12    finally
13    {
14        _isBusy = false;
15    }
16}

You can also disable the triggering control during execution for better UX.

Testing Strategy

Do not try to test async void directly when avoidable. Test the extracted Task methods.

csharp
1[Fact]
2public async Task RefreshAsync_LoadsData()
3{
4    var vm = new DashboardViewModel(fakeApiClient);
5    await vm.RefreshAsync();
6    Assert.True(vm.Items.Count > 0);
7}

This keeps tests deterministic and aligned with task based async behavior.

Library and Service Code Rule

For libraries, repositories, services, and controllers, return Task or Task of value. async void in these layers is almost always a design bug.

If you need fire and forget behavior, create a supervised background mechanism with explicit logging and exception capture rather than raw async void.

Common Pitfalls

A common pitfall is writing large business workflows directly inside an event handler. This hides logic behind UI signatures and makes failures harder to test.

Another issue is missing exception handling in event handlers, which leads to fragile behavior under real network failures.

Teams also forget reentrancy controls, causing duplicate submissions when users click quickly.

Summary

  • Avoid async void for normal methods and return Task instead.
  • Use async void only for framework event handler signatures.
  • Keep handlers thin and delegate real work to task returning methods.
  • Catch and log exceptions locally inside event handlers.
  • Add reentrancy guards to prevent overlapping user actions.

Related reading
Course
Intermediate
27 lessons
14 hours
OOD Fundamentals

Master object-oriented design from first principles, SOLID, design patterns, and classic interview problems with hands-on coding.

View the course
Track what you have practised

A free account saves your progress, solutions and study plan across every problem on Codemia.

Interview Questions practice on Codemia

Over 8,000 real interview questions from top companies, searchable by company and role.

Browse interview questions

All Rights Reserved.