C# async task and async void diference

The key difference between async Task and async void in C# lies in how they behave, how errors are handled, and when each should be used.


:counterclockwise_arrows_button: Summary Table

Feature async Task async void
Return type Returns a Task (can await) Returns nothing (void)
Can be awaited :white_check_mark: Yes :cross_mark: No
Exception handling :white_check_mark: Caught by try/catch when awaited :cross_mark: Uncaught exceptions crash app
Use case :white_check_mark: Regular async methods :warning: Event handlers only
Testable / chainable :white_check_mark: Can be awaited/tested :cross_mark: Not test-friendly

:test_tube: Example: async Task

public async Task DoSomethingAsync()
{
    await Task.Delay(1000);
    Console.WriteLine("Done");
}

// Usage
await DoSomethingAsync();  // Can be awaited
  • You can await it.
  • Exceptions can be caught with try/catch.

:prohibited: Example: async void

public async void DoSomething()
{
    await Task.Delay(1000);
    Console.WriteLine("Done");
}

// Usage
DoSomething(); // Cannot be awaited
  • Can’t await it.
  • If an exception occurs, it will bypass try/catch and potentially crash the application.
  • Only appropriate for event handlers like:
button.Click += async (sender, e) => {
    await Task.Delay(1000);
    Console.WriteLine("Clicked");
};

:warning: Rule of Thumb

:white_check_mark: Use async Task for almost everything.
:cross_mark: Use async void only for event handlers.


Let’s go through real-world examples to show the difference between async Task and async void, especially around exception handling and event handlers.


:white_check_mark: async Task Example with Exception Handling

public async Task LoadDataAsync()
{
    await Task.Delay(500); // Simulate work
    throw new InvalidOperationException("Something went wrong");
}

// Caller method
public async Task RunAsync()
{
    try
    {
        await LoadDataAsync(); // Can await
    }
    catch (Exception ex)
    {
        Console.WriteLine($"Caught: {ex.Message}"); // Will catch the exception
    }
}

:white_check_mark: Output:

Caught: Something went wrong

:locked: Safe and testable.
:white_check_mark: Exception is caught in try/catch because we awaited the Task.


:cross_mark: async void Example with Exception Handling (Fails)

public async void LoadData()
{
    await Task.Delay(500);
    throw new InvalidOperationException("Oops!");
}

public void Run()
{
    try
    {
        LoadData(); // No await here
    }
    catch (Exception ex)
    {
        Console.WriteLine($"Caught: {ex.Message}"); // This will NOT run
    }
}

:cross_mark: Output:

  • The app crashes or logs an unhandled exception.

:firecracker: Why?
You can’t await void, so the method continues without waiting β€” and exceptions escape the try/catch.


:white_check_mark: async void for UI Event Handler (Valid Use Case)

private async void Button_Click(object sender, EventArgs e)
{
    await Task.Delay(500);
    MessageBox.Show("Button clicked!");
}

:white_check_mark: This is OK:

  • Events require void return type.
  • async void is the only choice.
  • Exceptions still need special handling (e.g. try/catch inside the method).

:shield: Exception Handling Inside an async void Event Handler

private async void Button_Click(object sender, EventArgs e)
{
    try
    {
        await Task.Delay(500);
        throw new Exception("Button failed!");
    }
    catch (Exception ex)
    {
        MessageBox.Show($"Error: {ex.Message}");
    }
}

:locked_with_key: Best practice: Always use try/catch inside async void event handlers.


:brain: TL;DR

Situation Use
Regular async work async Task
Want to await it later async Task
Event handler (UI, events) async void
Method throws exception Avoid async void unless it’s an event