5

I'm not a big fan of quietly swallowed exceptions, but the following code does exactly that:

Task.Run(() =>
{
    var obj = DoSomethingCpuIntensive(); // returns null, due to a bug
    obj.DoMoreStuff();
    Console.WriteLine("after"); // never get here; program continues running
});

I've read about the ThrowUnobservedTaskExceptions configuration value, but this doesn't help, since I never do anything with the Task returned (edit: actually it does help, but only in Release builds).

Is there a way to make that unhandled exception crash the program? Am I using Task.Run in a way I'm not supposed to?

Roman Starkov
  • 59,298
  • 38
  • 251
  • 324
  • I'm pretty sure this will crash your program when the task is garbage collected – Johan Larsson Apr 25 '13 at 10:52
  • you should not throw a new Exception object, use one of the derived classes like ApplicationException, SecurityException etc. your question is interesting, usually I need to make extra effort to avoid the whole program to crash when a task / thread throws an exception, now you have all of that already in place and still want the task to crash everything... – Davide Piras Apr 25 '13 at 10:52
  • @DavidePiras That was just an example. I've made it clearer. – Roman Starkov Apr 25 '13 at 10:55
  • @JohanLarsson I added a GC.Collect() a few seconds after this `Task.Run` call. No crash of any kind. I should mention I'm on .NET 4.5. – Roman Starkov Apr 25 '13 at 10:56
  • Please note that GC.Collect is just a recommendation for garbage collection, it does not actually force it. And there are plenty of things to know about, e.g. generations in garbage collection etc – Mare Infinitus Apr 25 '13 at 10:59
  • If you are on 4.5 you can check out [await](http://msdn.microsoft.com/en-us/library/vstudio/hh156528.aspx), I have not used it enough to give any help. – Johan Larsson Apr 25 '13 at 11:05

3 Answers3

2

I strongly recommend you use Task.Run over ThreadPool.QueueUserWorkItem.

First, take a step back. What's the purpose of DoSomethingCpuIntensive? If you're calculating some value, I suggest you return it, so you have a Task<T> instead of a Task. e.g., (assuming DoMoreStuff is not CPU intensive):

async Task DoWorkAsync()
{
  var obj = await Task.Run(() => DoSomethingCpuIntensive());
  obj.DoMoreStuff();
}

The idea I'm trying to get across is that your code should care about the results of your background operations, even if the "result" is just "success" or "exception". Your code is cleaner that way. Otherwise, you've got a semi-independent system that you can only respond to by detecting changes in your application state. Much more messy.

That said, if you really do want to have the semi-independent system and you want it to crash your process if it fails, then ThrowUnobservedTaskExceptions is exactly what you want. I'm not sure why you think it wouldn't help.

Stephen Cleary
  • 437,863
  • 77
  • 675
  • 810
  • I think you missed the whole point. Even if you move the `obj.DoMoreStuff` outside of the task, the problem still exists inside `DoSomethingCpuIntensive`. [Never assume that any part of the code cannot possibly throw](http://i.imgur.com/7xPKTxg.png) :) – Timwi Apr 25 '13 at 13:01
  • My point is that if you do it this way, it doesn't matter where it throws. The exception is properly propagated back rather than directly crashing the process. – Stephen Cleary Apr 25 '13 at 13:04
  • I know what you're saying, but there really do exist fire-and-forget tasks in programming... This task in particular does something CPU intensive and then saves it to the database. The caller doesn't care about the result, nor has anything at all to do with it. – Roman Starkov Apr 30 '13 at 13:02
  • Make sure you're putting a `Thread.Sleep` in your test code. [See Reed's answer here](http://stackoverflow.com/questions/3284137/taskscheduler-unobservedtaskexception-event-handler-never-being-triggered). – Stephen Cleary Apr 30 '13 at 13:36
  • Apparently you also need to run a Release build. Debug builds don't do it. Reed's answer doesn't mention that, but MSDN on `ThrowUnobservedTaskExceptions` does, in the Example section. How subtle. With this in mind, and everything I said before (about fire-and-forget, and having no answer to be returned), I really think `QueueUserWorkItem` is a much better fit. Do you have a specific reason why despite these shortcomings I should use `Task.Run`? (might be better as a separate question...) – Roman Starkov Apr 30 '13 at 13:57
  • If it is *truly* fire-and-forget, then `QueueUserWorkItem` is fine. I always avoid fire-and-forget; at the very least I prefer to take action on errors (and a global solution like `UnhandledTaskException` uglifies my design). So that's why I recommend `Task.Run` in general: if there's a result, or an error, or you want to cancel it, or report progress, or synchronize the result/error/progress, then `Task` is superior. But if you are absolutely sure you don't want to report errors, and you just want to crash instead, then `QueueUserWorkItem` is a perfect fit. It makes it *easy* to crash! ;) – Stephen Cleary Apr 30 '13 at 14:19
  • Understood, thanks. I prefer a truly global solution like `AppDomain`s `UnhandledException`, with special-purpose handling for when I'm sure I can recover reliably or have specific additional details to log. Although in this particular case it was a throw-away single-use program which wasted me half an hour by pretending to do something instead of crashing. – Roman Starkov Apr 30 '13 at 18:16
1

It appears that using Task.Run like this is, indeed, a mistake. I think I'm supposed to await it, else silly things like the above will happen.

When it does crash with the await, the debugger is rather unhelpful, looking like this:

                enter image description here

I take this all to mean I'm really doing the wrong thing with Task.Run here.

A simple fix is use the good old ThreadPool.QueueUserWorkItem instead:

                  enter image description here

That's much better! I didn't really need async/await in this code anyway; I just used Task.Run because it's less typing.

Roman Starkov
  • 59,298
  • 38
  • 251
  • 324
  • @Timwi It's almost the same; `Task.Run` is a shorthand for `StartNew` with some parameters set to defaults. – Roman Starkov Apr 30 '13 at 14:07
  • I'm following your approach but it is a shame that the new libraries work worse than the previous ones ... – Ignacio Soler Garcia Jan 08 '15 at 14:30
  • Ey, I found how to do it using tasks. Please check the following link: http://stackoverflow.com/questions/27856784/how-to-make-an-application-crash-when-a-task-throws-an-exception-without-waiting/27863178#27863178 – Ignacio Soler Garcia Jan 09 '15 at 14:50
0

You say that you've read about ThrowUnobservedTaskExceptions; have you actually tried running your program with ThrowUnobservedTaskExceptions set to true in your config?

The default behaviour in .NET 4 is that unobserved Task exceptions will bring down the entire process.

The default behaviour in .NET 4.5 is that unobserved Task exceptions will not bring down the process. Since you're running on .NET 4.5, setting ThrowUnobservedTaskExceptions to true should do exactly what you require.

LukeH
  • 263,068
  • 57
  • 365
  • 409