Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Task unhandled exceptions

Tags:

c#

exception

task

I'm trying to understand what is going on with exceptions that are thrown within a task object and never handled.

On MSDN it said that:

If you do not wait on a task that propagates an exception, or access its Exception property, the exception is escalated according to the .NET exception policy when the task is garbage-collected.

So I don't quite understand in what way those exceptions affect program flow. I thought that those exceptions should interrupt execution as soon as they are garbage-collected. But I can not design this behaviour. Within the following snippet the thrown exception doesn't show up.

// Do something ...
Task.Run (()=> {throw new Exception("Exception in the task!");});
// Do something else

Please, can anyone explain how unhandled task exception are dealt with and how they affect program flow.

like image 903
user3101007 Avatar asked Feb 08 '14 15:02

user3101007


People also ask

What does an unhandled exception mean?

An unhandled exception is an error in a computer program or application when the code has no appropriate handling exceptions. Learn about the definition and examples of unhandled exceptions, and explore programming and exception handlers. Updated: 01/04/2022.

What happens when a task throws an exception?

When exceptions happen, all the exceptions are re-thrown by the calling thread. To do that they're wrapped inside AggregateException and returned to the caller. So when we await a task, we only get the first exception from a collection of exceptions that might exist on a task.

What happens to an unhandled exception?

If an unhandled exception condition happens in a secondary thread and moves all the way to the first invocation in the thread without being handled, the resulting action will be to terminate the thread. During this percolation, if the exception hits a control boundary and is not handled, it may terminate the process.


2 Answers

You're describing the behavior as it was in .NET 4, but it will be difficult for you to force the garbage collection and actually observe that behavior. The following quote from Stephen Toub's excelent write-up on the subject should make it even more clear:

Tasks keep track of whether an unhandled exception has been “observed.” In this context, “observed” means that code has joined with the Task in some fashion in order to at least be made aware of the exception. This could be calling Wait/WaitAll on the Task. It could be checking the Task’s Exception property after the Task has completed. Or it could be using a Task’s Result property. If a Task sees that its exception has been observed in some manner, life is good. If, however, all references to a Task are removed (making the Task available for garbage collection), and if its exception hasn’t yet been observed, the Task knows that its exception will never be observed. In such a case, the Task takes advantage of finalization, and uses a helper object to propagate the unhandled exception on the finalizer thread. With the behavior described earlier, that exception on the finalizer thread will go unhandled and invoke the default unhandled exception logic, which is to log the issue and crash the process.

He also suggested two useful extension methods for handling exceptions in "fire-and-forget" tasks: one ignoring the exception and the other one immediately crashing the process:

public static Task IgnoreExceptions(this Task task)
{
    task.ContinueWith(c => { var ignored = c.Exception; },
        TaskContinuationOptions.OnlyOnFaulted |
        TaskContinuationOptions.ExecuteSynchronously |
        TaskContinuationOptions.DetachedFromParent);
    return task;
}

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

In .NET 4.5 the default behavior has changed. Again, a quote from another Stephen Toub's post on the subject (thanks to mike z for bringing it to my attention in the comments):

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. This behavior can be configured, though.

like image 86
Damir Arh Avatar answered Oct 07 '22 05:10

Damir Arh


Please note the code above is not quite correct. You must return the pointer to the task.ContinueWith, not the passed-in task:

public static Task IgnoreExceptions(this Task task)
{
    var t = task.ContinueWith(c => { var ignored = c.Exception; },
        TaskContinuationOptions.OnlyOnFaulted |
        TaskContinuationOptions.ExecuteSynchronously);
    return t;
}

EDIT:

This is challenging because it depends on how you chain your calls together. For example, the following call doesn't work the way I would expect:

public Task MyServiceCall()
{
  return Task.Run(() => DoSomething()).IgnoreExceptions();
}

This method does indeed throw exceptions because the the accepted answer returns the initial task (not the one which observes the exception). This could be problematic with other calls, such as .Wait, .WhenAll etc. One might think that the task will never throw, but it can.

However, my suggested change would break the following:

public void SomeMethod()
{
  var myTask = new Task(() => ...);
  myTask.IgnoreExceptions().Start();
}

Internally we've decided to obsolete this extension method as it's too confusing!

like image 28
user2116722 Avatar answered Oct 07 '22 04:10

user2116722