Should the async task library swallow exceptions quietly

net

I've just learned that .NET 4.5 introduced a change to how exceptions inside a Task are handled. Namely, they are quietly suppressed.

The official reasoning for why this was done appears to be "we wanted to be more friendly to inexperienced developers":

In .NET 4.5, Tasks have significantly more prominence than they did in .NET 4, as they’re baked in to the C# and Visual Basic languages as part of the new async features supported by the languages. This in effect moves Tasks out of the domain of experienced developers into the realm of everyone. As a result, it also leads to a new set of tradeoffs about how strict to be around exception handling.

(source)

I've learned to trust that many of the decisions in .NET were made by people who really know what they're doing, and there is usually a very good reason behind things they decide. But this one escapes me.

If I were designing my own async task library, what is the advantage of swallowing exceptions that the developers of the Framework saw that I'm not seeing?

Best Answer

For what it's worth, the document you linked to gives an example case as justification:

Task op1 = FooAsync(); 
Task op2 = BarAsync(); 
await op1; 
await op2;

In this code, the developer is launching two asynchronous operations to run in parallel, and is then asynchronously waiting for each using the new await language feature...[C]onsider what will happen if both op1 and op2 fault. Awaiting op1 will propagate op1’s exception, and therefore op2 will never be awaited. As a result, op2’s exception will not be observed, and the process would eventually crash.

To make it easier for developers to write asynchronous code based on Tasks, .NET 4.5 changes the default exception behavior for unobserved exceptions. While unobserved exceptions will still cause the UnobservedTaskException event to be raised (not doing so would be a breaking change), the process will not crash by default. Rather, the exception will end up getting eaten after the event is raised, regardless of whether an event handler observes the exception.

I'm not convinced by this. It removes the possibility of an unambiguous, but hard to trace error (mysterious program crash that might occurs long after the actual error), but replaces it with the possibility of a completely silent error--which might become an equally hard to trace problem later on in your program. That seems like a dubious choice to me.

The behavior is configurable--but of course, 99% of developers are just going to use the default behavior, never thinking about this issue. So what they selected as the default is a big deal.