Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Silent exceptions in Task.Factory.StartNew when using a long running background consumer task?

This notifies of an unhandled exception:

new Thread(_ => { throw new Exception(); }).Start();

This does not (at least until you wait/retrieve the result):

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

Why? What has happened to the thread on which the exception was thrown? Does it die?

This is a problem where you run a task but don't need its result, or need to wait on it, like this:

_operationQueue = new BlockingCollection<Operation>();

Task.Factory.StartNew(() => 
{
     foreach (var item in _operationQueue.GetConsumingEnumerable())
     {
         // do something that throws
     }
}, TaskCreationOptions.LongRunning);

In this case, what state is the _operationQueue left in?

I know I can use a Continuation with TaskContinuationOptions.OnlyOnFaulted, can you just resume processing?

like image 785
jimmy_terra Avatar asked Jun 17 '13 15:06

jimmy_terra


2 Answers

Well, what are you assuming should happen instead? Are you thinking that whenever an exception is thrown in another thread it should immediately propagate to the thread that started that task? I strongly disagree. First off, the code in the calling thread would then be forced to abort the operation it's in the middle of, and that's rather likely to cause significant problems. Just look into all of the posts surrounding Thread.Abort to see all of the very significant problems that arise when you allow an exception to be thrown at some arbitrary point in a program's execution instead of at certain known points.

If you're suggesting that the entire program should crash when a task's code throws an exception, then I'd say that by and large that's simply not desirable. In the rare case that it is, you can (rather easily) create a continuation on the task that does end the entire process if the task is faulted. I've yet to to need to create such a continuation myself though. If the system were designed to bring down the process when a task threw an exception then getting the opposite behavior wouldn't be nearly as easy.

What has happened to the thread on which the exception was thrown? Does it die?

If the exception is caught then execution continues after the catch; if it propagates through the entire call stack then the exception will be caught by code within the Task framework which wraps the exception and makes it available to continuations.

In this case, what state is the _operationQueue left in?

It's a perfectly fine queue, that has had 1..N items removed from it. If the body of the loop always throws then one item will have been taken from it. If it only sometimes throws, then some number of items will be taken from it. The remaining items are still in the queue and can be removed by any other thread with access to it. If the queue is no longer accessible then it would become eligible for garbage collection.

I know I can use a Continuation with TaskContinuationOptions.OnlyOnFaulted, can you just resume processing?

The calling thread can; sure. The task itself would only be able to continue by having a try/catch within it's delegate. If the exception has been thrown to the point that the task is faulted nothing would allow that task to continue executing.

like image 61
Servy Avatar answered Nov 14 '22 21:11

Servy


The exception in the task is caught by the TaskScheduler. If you never want to observe the result of a task, but would like to be notified on unhandled exceptions in tasks, there is the TaskScheduler.UnobservedTaskException event. Note that this event does not fire immediately, but upon finalization of the Task if the exception has never been retrieved. In .Net 4, unobserved task exceptions were rethrown and became unhandled exceptions that ended the process, but this was changed in .Net 4.5.

like image 35
Mike Zboray Avatar answered Nov 14 '22 22:11

Mike Zboray