15

I am building a Windows Service with .NET 4.0.

I have various unhandled exceptions thrown in Tasks, but they do not terminate my process as the MSDN documentation states (Parallel Tasks - see Unobserved Task Exceptions).

"If you don't give a faulted task the opportunity to propagate its exceptions (for example, by calling the Wait method), the runtime will escalate the task's unobserved exceptions according to the current .NET exception policy when the task is garbage-collected."

It behaves like this even when I use the most simple invokation of a task:

Task.Factory.StartNew(() => { throw new Exception(); } 

The service keeps on running fine when that is called.

According to the docs, the finalizer of the Task will rethrow the exception once the Task is GC'd but this does not appear to happen. MSDN states repeatedly that normal ".NET exception policy" results in process termination.

Why doesn't this terminate my app? The only thing I can think is there is somehow a reference to the task held somewhere (is it the lambda??)

svick
  • 236,525
  • 50
  • 385
  • 514
Jack Ukleja
  • 13,061
  • 11
  • 72
  • 113
  • 1
    Did you force a GC? It's possible that the finalizers just didn't run yet. – CodesInChaos Aug 08 '11 at 13:06
  • I am going to try testing GC.Collect again. Think i cocked it up last time by calling it from ContinueWith... – Jack Ukleja Aug 08 '11 at 13:09
  • Interestingly, I am looking through the Task.cs source code (from MS server and Reflector) and I cannot see a finalizer in the code...am I being blind, or is there another way to finalize it, or is the docs just wrong? – Jack Ukleja Aug 09 '11 at 00:29

5 Answers5

10

From Essential C# 4.0, page 715, the following might help you out:

The unhandled exception during the Task's execution will be suppressed until a call to one of the task completion members: Wait(), Result,Task.WaitAll(), or Task.WaitAny(). Each ot these members will throw any unhandled exceptions that occurred within the task's execution.

Quite a few ways of handling exceptions are available. Have a look at MSDN here.

In answer to your comment, another quote from the same book explains why some exceptions are not propagated:

Although relatively rare, one of the exceptions for the general rule (of bubbling up) happens to be on Task. [..] Any Task-based exceptions thrown from the finalization queue during application exit will go suppressed. The behavior is set this way because frequently the effor to handle such an exception it too complex [...]

To overcome this, one way to do this elegantly is to create an exception handler task and to use ContinueWith to follow up after your task runs. You can then use parentTask.IsFaulted and gracefully crash, even in the event the exception is thrown in the finalization queue during application exit.

Tip: use the the flag OnlyOnFaulted to have this task run only when an exception occurs.

Jack Ukleja
  • 13,061
  • 11
  • 72
  • 113
Abel
  • 56,041
  • 24
  • 146
  • 247
  • 2
    Yup, im aware. But I'm not looking to wait on the tasks. According to the docs, it should throw once its finalized – Jack Ukleja Aug 08 '11 at 13:02
  • The point is, that generally exceptions are _not_ propagated to the parent in the case of a Task, not always, I mean. – Abel Aug 08 '11 at 13:05
  • "If you don't give a faulted task the opportunity to propagate its exceptions (for example, by calling the Wait method), the runtime will escalate the task's unobserved exceptions according to the current .NET exception policy when the task is garbage-collected." – Jack Ukleja Aug 08 '11 at 13:06
  • Hmm interesting, but seems back to front in my scenario. Makes sense they are supressed on app exit, but I want them to cause app exit. I'm thinking this is GC related. Because no GC is happening the Task exceptions are effectively surpressed. – Jack Ukleja Aug 08 '11 at 13:14
  • @Schneider: I'd be interested to know whether or not triggering the GC helped. – Abel Aug 08 '11 at 13:39
  • 1
    It will, call GC.WaitForPendingFinalizers as well. Of course, the rub is to *when* call that. Having to wait on a task tends to defeat the point of using one. – Hans Passant Aug 08 '11 at 14:09
  • 1
    An update from manys years later: I'm a regular user of TPL and the way I'm handling "unhandled" exceptions for tasks (where I care) is to use this `ContinueWith/OnlyOnFaulted` combo. I fire a custom `TaskTerminated` event, and if I want to terminate my process I do it using that. – Jack Ukleja Jul 06 '16 at 01:49
6

.NET 4.5 made some changes as to how UnobservedExceptions are handled

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.

This behavior can be configured though, so you can revert back to .Net 4.0 behavior by enabling ThrowUnobservedTaskExceptions like so:

<configuration> 
    <runtime> 
        <ThrowUnobservedTaskExceptions enabled="true"/> 
    </runtime>
</configuration>

It's recommended library developers enable this when testing to ensure they don't have any UnobservedExceptions being thrown. Otherwise library consumers with this setting enabled might see their programs crashing.

Jack Ukleja
  • 13,061
  • 11
  • 72
  • 113
  • Mine still crashing in .NET 4.6, regardless of this setting. After calling GC.Collect, there is no UnobservedTaskException event and the process just exits immediately with code zero. – Triynko Nov 24 '18 at 03:38
5

As suggested by @Hans and @CodeInChaos I found the only way to rethrow the unhandled exception (thus killing the process) is to force the Finalizer to run (Note: Make sure you dont do this in the ContinueWith()!):

GC.Collect(); 
GC.WaitForPendingFinalizers();

In my particular circumstances the Task was not garbage collected because the flow of the program depended on the Task being succesful. Without the flow continuing my app would not do anything to cause a GC (allocate objects etc).

What is interesting is that even doing a GC.Collect() is not enough. The Task finalizer still did not run. The GC.WaitForPendingFinalizers() had to be called explicitly. (I suspect I do not understand subtleties around Finalization).

To summarize: Dont expect a TPL Task's unobserved exception behaviour to be similar to other threading mechanisms unhandled exception behaviour (e.g. QueueUserWorkItem). In most practical situations you need to explicity check for Exceptions from Tasks: you cannot rely on unobserved exceptions being brought to your attention in the way they would with a QUWI or similar because you will only see them thrown from the Finalizer which is totally unpredictable.

Edit: See my other answer concerning .NET 4.5

Jack Ukleja
  • 13,061
  • 11
  • 72
  • 113
  • 1
    Unobserved exceptions in a task usually indicate a bug. They are not an error reporting mechanism. Thus there is no need for a very reliable mechanism. – CodesInChaos Aug 09 '11 at 08:07
  • So now we have two reasons why an exception doesn't bubble up in Tasks: the finalizer hasn't run or the exception occurs in the finalization queue during application shutdown. – Abel Aug 09 '11 at 16:59
  • 2
    @CodeInChaos. Are you aware of the sweeping changes made in .NET 2.0 to the exception policy? They made all unhandled exceptions crash the process, precisely because people were not aware of exceptions happening. In other words they were "unobserved". Why should the TPL not follow the same principle of the framework of letting users know an exception has occurred? Of course unobserved exceptions indicate a bug. And I want to know about bugs (by default), not have them hidden. – Jack Ukleja Aug 10 '11 at 02:02
  • I was able to get this working by using ContinueWith, but then invoking on the UI thread. – juharr Sep 19 '13 at 15:13
0

According to this nice blog post if you want to crash your app as soon as you have unhandled exception in your task then you can continue your task something like below:

public static Task FailFastOnException(this Task task) 
{ 
    task.ContinueWith(c => Environment.FailFast(“Task faulted”, c.Exception), 
    TaskContinuationOptions.OnlyOnFaulted | 
    TaskContinuationOptions.ExecuteSynchronously | 
    TaskContinuationOptions.DetachedFromParent); 
    return task; 
}
Gurmit Teotia
  • 179
  • 2
  • 7
0

You may create it with TaskCreationOptions.AttachedToParent. According to Nested Tasks and Child Tasks (MSDN), Exceptions are then propagated to your thread. I do not know, however, whether this is elegant or not.

Microsoft does not recommend this "in most cases". Somebody else might know in what case this may be sensible. From the same article:

You can use attached child tasks to create tightly-synchronized graphs of asynchronous operations. However, in most scenarios, we recommend that you use nested tasks because the relationships with other tasks are less complex. That is why tasks created inside other tasks are nested by default, and you must explicitly specify the AttachedToParent option to create a child task.

Cheers, Matthias

Matthias Meid
  • 12,455
  • 7
  • 45
  • 79