Reputation: 106926
My code combines async methods with exception handling. The code is very simple and all tasks are awaited and there are no async void
methods:
async Task DoWorkSafelyWithStateMachine()
{
try
{
await DoWorkThatMightThrowException();
}
catch (Exception exception)
{
Console.WriteLine("With state machine: " + exception.Message);
}
}
Awaiting this method does not throw an exception because the exception was swallowed:
await DoWorkSafelyWithStateMachine(); // No exception thrown
However, the code does not always catch exceptions as it is supposed to do. The problem arises when the method is written in a slightly different way where no async state machine is created by the compiler:
Task DoWorkSafelyWithoutStateMachine()
{
try
{
return DoWorkThatMightThrowException();
}
catch (Exception exception)
{
Console.WriteLine("Without state machine: " + exception.Message);
return Task.CompletedTask;
}
}
The method is not decorated with async
and nothing is awaited inside the method. Instead the task returned by the method inside try
is returned to the caller. However, in my experience the magic of the compiler somehow still ensures that if the method inside try
throws an exception it gets caught by the exception handler. Well, apparently that is not always true.
To test these two variations of the same method I let DoWorkThatMightThrowException
throw an exception. If the method does not have to use await
in the body of the method then it can be implemented either with or without an async state machine:
async Task DoWorkThatMightThrowExceptionWithStateMachine()
{
throw new Exception("With state machine");
await Task.CompletedTask;
}
Task DoWorkThatMightThrowExceptionWithoutStateMachine()
{
throw new Exception("Without state machine");
return Task.CompletedTask;
}
I have discovered that calling DoWorkThatMightThrowExceptionWithStateMachine
from DoWorkSafelyWithoutStateMachine
does not catch the exception thrown. The other three combinations do catch the exception. In particular, the version where no async state machines are involved in either method catches the exception and I have mistakenly extrapolated this observation with the unfortunate result that some of my code now has subtle errors.
| Throw + state machine | Throw - state machine | --------------------------+-----------------------+-----------------------+ Try/catch + state machine | Caught | Caught | Try/catch - state machine | Not caught | Caught |
Doing this experiment I have learned that I always have to await
a task inside a try
block (first line in table). However, I don't understand the inconsistency in the second row of the table. Please explain this behavior. Where is it documented? It is not easy to search for information about this. The more basic problem of exceptions being "lost" because tasks are not awaited will dominate the search results.
Upvotes: 3
Views: 3266
Reputation: 43936
I don't understand the inconsistency in the second row of the table
The right side case (no state machines at all) is trivial:
try/catch
blockcatch
black catches it - nothing specialThe left side is actually easy to understand, too:
try/catch
blockTask
that represents the execution of the method's content as you implemented it(1)Wait
or await
that returned Task
or try to access its Result
property, the exception is not (re-)thrown inside your try
block.(1) I wish my English was better to find a better and more exact description. As Servy pointed out, in your example an already faulted Task
is returned.
Upvotes: 4
Reputation: 203812
The catch
block will execute when an exception is thrown in the try
block. When you write return DoWorkThatMightThrowExceptionWithStateMachine();
all the try block is doing is constructing a Task
marked as faulted and returning it. No exception is ever thrown, so no catch
block is ever run.
If you await the faulted task it will re-throw that exception, so there is an exception being thrown. When you call DoWorkThatMightThrowExceptionWithoutStateMachine
the method itself is throwing an exception, not returning a faulted task, and so there's an exception being thrown in the try
block.
Upvotes: 5