🎓 A1Learner
🔍
0 of 28 lessons completed

C# Async Exceptions

An async exception is an exception thrown inside an async method and stored in the returned task. Awaiting that task throws the stored exception again at the await point.

Caught at the Await

The Fail method throws after an await. Main awaits Fail inside a try block and catches the exception.

using System;
using System.Threading.Tasks;

class Program
{
    static async Task Fail()
    {
        await Task.CompletedTask;
        throw new InvalidOperationException("Boom");
    }

    static async Task Main(string[] args)
    {
        try
        {
            await Fail();
        }
        catch (InvalidOperationException ex)
        {
            Console.WriteLine("Caught: " + ex.Message);
        }
    }
}

Two Faults, Two Types

Two methods throw two different exception types. Each await sits in its own try block, so each type is caught on its own.

using System;
using System.Threading.Tasks;

class Program
{
    static async Task FailFirst()
    {
        await Task.CompletedTask;
        throw new InvalidOperationException("First fault");
    }

    static async Task FailSecond()
    {
        await Task.CompletedTask;
        throw new ArgumentException("Second fault");
    }

    static async Task Main(string[] args)
    {
        try
        {
            await FailFirst();
        }
        catch (InvalidOperationException ex)
        {
            Console.WriteLine("Caught: " + ex.Message);
        }

        try
        {
            await FailSecond();
        }
        catch (ArgumentException ex)
        {
            Console.WriteLine("Caught: " + ex.Message);
        }
    }
}

One Fault in a Group

One task returns a value and one task faults. Main awaits the WhenAll group inside a try block.

using System;
using System.Threading.Tasks;

class Program
{
    static async Task<string> Good()
    {
        await Task.CompletedTask;
        return "Good done";
    }

    static async Task<string> Bad()
    {
        await Task.CompletedTask;
        throw new InvalidOperationException("Boom");
    }

    static async Task Main(string[] args)
    {
        Task<string> good = Good();
        Task<string> bad = Bad();

        try
        {
            await Task.WhenAll(good, bad);
        }
        catch (InvalidOperationException ex)
        {
            Console.WriteLine("Caught: " + ex.Message);
        }
    }
}

Example

using System;
using System.Threading.Tasks;

class Program
{
    static async Task<string> Fetch(string name)
    {
        await Task.CompletedTask;

        if (name == "missing")
        {
            throw new InvalidOperationException("Not found");
        }

        return "Data for " + name;
    }

    static async Task Main(string[] args)
    {
        Console.WriteLine(await Fetch("report"));

        try
        {
            Console.WriteLine(await Fetch("missing"));
        }
        catch (InvalidOperationException ex)
        {
            Console.WriteLine("Caught: " + ex.Message);
        }
    }
}

Key points

  • An exception inside an async method does not throw at the call site.
  • The returned task stores the exception instead.
  • Awaiting a faulted task throws the stored exception at the await.
  • The thrown exception keeps its original type and message.
  • A try block around the await catches it like a normal exception.
  • Task.WhenAll waits for every task in the group, even when one task faults.
  • An await on a faulted WhenAll task throws the first fault.

In this example, Fetch returns a string for the name report. The name missing makes Fetch throw an InvalidOperationException after its await. Main catches that exception at the second await and prints its message.

Note

Each snippet in this lesson catches at the await, so no exception escapes Main.

Try it Yourself

Edit the code, then press Run.

Running is simulated

Exercise

Where does an exception from an async method surface for the caller?