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?
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 await
ed. Most of the time, the task is await
ed 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
.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With