Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

C# Async Exception Wrapping

I have been playing around with async calls in .NET 4.6.1 and I am wondering what the correct way to throw errors is from an implementer of an interface that expects an asynchronous method but is in fact synchronous. For example:

public interface ISomeInterface
{
    Task ExecuteAsync();
}

public class SomeClass : ISomeInterface
{
    public Task ExecuteAsync()
    {
        return Task.FromException(new Exception());
    }
}

I found Task.FromException here.

So this is .NET 4.6 still seemingly advising to wrap exceptions. However I could just write the following code:

public class SomeClass : ISomeInterface
{
    public Task ExecuteAsync()
    {
        throw new Exception();
    }
}

When I called this second implementation using a try/catch block, the client caught the Exception, which I thought was why we used Task.FromException in the first place, and what is more it also contains the entire call stack to the original exception (whereas approach one only has a stack trace to the await operation of the client). It seems then that the second approach is the better, and yet everyone seems to be using approach one. Is approach one now obsolete because of changes to the implementation of async, or is there something I am missing?

I also noticed in the stack trace that async methods don't introduce any additional frames between calls now. I am assuming that this is just to simplify reading the stack trace?

like image 830
Matt Avatar asked Oct 18 '22 22:10

Matt


1 Answers

what the correct way to throw errors is from an implementer of an interface that expects an asynchronous method but is in fact synchronous.

As you discovered, you can either throw the exception directly or place the exception on the returned Task.

Note that this does change where the exception is observed:

var task = obj1.ExecuteAsync();
await task;

If the exception is thrown directly, it is thrown at the time ExecuteAsync is called. If the exception is placed on the returned task, it is thrown at the time that task is awaited. Most of the time, the task is awaited immediately after the method is called, but not always (e.g., in Task.WhenAll kinds of scenarios).

With asynchronous (Task-returning) APIs, the returned task represents the execution of the method. async-implemented APIs always place any exceptions on the returned task. So, I would say that the expectation of a Task-returning API is that the task would receive the exception.

In the case of boneheaded exceptions, you could make the case either way. Since the exception indicates a code bug, it doesn't really matter when it's raised. LINQ to Objects, for example, will always raise boneheaded exceptions immediately, not when its returned enumerator is enumerated.

However, for all other kinds of exceptions, they should definitely go on the returned Task. Personally, I just put all exceptions on the returned Task.

like image 190
Stephen Cleary Avatar answered Nov 12 '22 21:11

Stephen Cleary