Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Try-Catch Async Exceptions

This example "fails":

static async void Main(string[] args)
{
    try
    {
        await TaskEx.Run(() => { throw new Exception("failure"); });
    }
    catch (Exception)
    {
        throw new Exception("success");
    }
}

That is, the exception with the text "failure" bubbles up.

Then I tried this workaround:

static async void Main(string[] args)
{
    try
    {
        await SafeRun(() => { throw new Exception("failure"); });
    }
    catch (Exception)
    {
        throw new Exception("success");
    }
}

static async Task SafeRun(Action action)
{
    var ex = default(Exception);
    await TaskEx.Run(() =>
    {
        try
        {
            action();
        }
        catch (Exception _)
        {
            ex = _;
        }
    });
    if (ex != default(Exception))
        throw ex;
}

That didn't help either.

I suppose my Async CTP refresh installation could be hosed.

Should this code work as I expect ("success" bubbles up, not "failure"), or is this not "supposed" to work that way. And if not, how would you work around it?

like image 623
Bent Rasmussen Avatar asked Jun 16 '11 19:06

Bent Rasmussen


People also ask

Does Try Catch work with async?

Since you're working with what looks like synchronous code, async functions can use try... catch to handle any errors. try... catch is the most common way to handle exceptions when using async/await.

How do you handle exceptions in async methods?

Handling Exceptions in Asynchronous Methods Returning a Task Object. If an exception is raised within an asynchronous method returning a Task object, the exception is wrapped into an instance of the AggregateException class and passed back to the calling method.

Do we need try catch for async await?

When we use async/await , we rarely need .then , because await handles the waiting for us. And we can use a regular try..catch instead of .catch .


1 Answers

The behavior you are seeing is likely an edge case bug or may even be correct, if unintuitive. Normally when you invoke an async method synchronously, it wraps a task around to execute and since there is no one waiting on the task to finish, the exception never makes it to the main thread. If you were to call Main directly it would succeed, but then your runtime would see an exception of "success" on another thread.

Since main is the entrypoint of your application, it is invoked synchronously and likely as the entrypoint doesn't trigger the Task wrapping behavior, so that await isn't run properly and the TaskEx.Run throws on its own thread, which shows up in the runtime as an exception being thrown on another thread.

If you were to run main as an async method, i.e. returning a Task (since an async that returns void can only really be called via await) and blocking on it from your synchronous main context, you would get the appropriate behavior as the below test illustrates:

static async Task Main() {
    try {
        await TaskEx.Run(() => { throw new Exception("failure"); });
    } catch(Exception) {
        throw new Exception("success");
    }
}

static async Task Main2() {
    await Main();
}

[Test]
public void CallViaAwait() {
    var t = Main2();
    try {
        t.Wait();
        Assert.Fail("didn't throw");
    } catch(AggregateException e) {
        Assert.AreEqual("success",e.InnerException.Message);
    }
    }


[Test]
public void CallDirectly() {
    var t = Main();
    try {
        t.Wait();
        Assert.Fail("didn't throw");
    } catch(AggregateException e) {
        Assert.AreEqual("success", e.InnerException.Message);
    }
}

I.e. the Task faults with an AggregateException which contains the success exception as it's inner exception.

like image 172
Arne Claassen Avatar answered Sep 19 '22 02:09

Arne Claassen