5

I have a scenario, where I create Tasks as a part of a Webapi call. When an exception happens in a task it does not get caught and I can't figure out how to implement a global exception handler for this.

In particular:

If you believe that any of the above should work let me know, and I'll come up with a small reproducible example of what I'm observing. Otherwise, what is the right way of doing this, that would work?

[HttpGet]
public IQueryable<Thingies> Thingies()
{
    Task.Run(() => { throw new ApplicationException("How do I catch all of these globally?"); });
    return _db.Thingies;
}

I also should note that the application does not have dependencies on System.Web and I would not like to introduce them.

Community
  • 1
  • 1
Andrew Savinykh
  • 25,351
  • 17
  • 103
  • 158
  • 1
    You can get back the NET 4.0 `AppDomain.CurrentDomain.UnhandledException` behavior by configuring the `ThrowUnobservedTaskExceptions` setting in app.config. See ["TAP global exception handler"](http://stackoverflow.com/questions/22369179/tap-global-exception-handler). That said, you shouldn't be creating tasks which span the scope of individual HTTP requests, especially in a fire-and-forget manner. Check Stephen Cleary's ["Fire and Forget on ASP.NET"](http://blog.stephencleary.com/2014/06/fire-and-forget-on-asp-net.html). – noseratio Aug 28 '15 at 02:47
  • @Noseratio thank you, this is helpful. Unfortunately it seems that *any* unhanded exceptions can't be caught by `AppDomain.CurrentDomain.UnhandledException` not just Task ones. That is if you are running it in webapi. See example [here](http://stackoverflow.com/questions/29257188/webapi-and-unhandled-exception-hook-per-appdomain) – Andrew Savinykh Aug 28 '15 at 04:40
  • Interesting and it makes sense I guess, for security reasons. I'm not an expert in ASP.NET but I think there should be a way to install a global exception handler from a global ASP.NETfilter. – noseratio Aug 28 '15 at 07:04
  • @Noseratio when you say filter you are probably thinking IIS. I'm looking for an answer not specific to IIS. If you mean something else, please clarify, because I did not understand you in this case. – Andrew Savinykh Aug 28 '15 at 07:08
  • @zespi, just to clarify, are you looking to catch exceptions in those background tasks *outside* HTTP request handler scopes? – noseratio Aug 28 '15 at 11:33
  • If you mean HTTP request handler in a broad sense, then yes. If you mean an [IHttpHandler](https://msdn.microsoft.com/en-us/library/system.web.ihttphandler(v=vs.110).aspx) such as [HttpApplication](https://msdn.microsoft.com/en-us/library/system.web.httpapplication(v=vs.110).aspx), etc then these are not applicable, since they are neither used nor referenced in the app. – Andrew Savinykh Aug 28 '15 at 11:42
  • Yes, in a broad sense. And I don't think you should be allowed to install a *global* exception handler from inside a WebAPI controller. It'd be a big security hole, allowing you to catch exceptions thrown by other fellow WebAPI controllers living in the same app domain. Now if you do have control over the hosting environment, you should be able to implement a host-specific handler either IIS filter or OWIN API perhaps (again, I'm not an expert here). – noseratio Aug 28 '15 at 11:56
  • So, if you do spawn background tasks, you'd observe their exception individually, e.g. with `ContinueWith`. You'd have to use something like `HostingEnvironment.QueueBackgroundWorkItem` anyway for each task. – noseratio Aug 28 '15 at 11:57

2 Answers2

3

Exceptions that are thrown from inside a running task aren't unhandled. They are captured on that task turning it faulted.

The correct way to handle these exceptions would be to await each task, or register continuations using Task.ContinueWith. However, if you want a global handler you can use the TaskScheduler.UnobservedTaskException event.

When there's an exception inside a task it's handled and stored on the Task object for you to observe. If the task gets garbage collected and the exception wasn't yet observed by any code .Net knows that it will never be observed (as you don't have a reference to the task) and raises the TaskScheduler.UnobservedTaskException event (in .Net 4.0 it will also crash the app and that behavior can be replicated in newer versions as well).

So, an event handler for TaskScheduler.UnobservedTaskException will globally handle all exceptions raised from tasks, but only after they have been GCed (which isn't immediately):

TaskScheduler.UnobservedTaskException += (sender, args) => HandleException(args.Exception);
i3arnon
  • 113,022
  • 33
  • 324
  • 344
  • Can you explain what triggers that event? I did some experimentation, and it looks that you are 100% correct, however when I do GC.Collect it does not immediately triggers the event, the event arrives some time later. – Andrew Savinykh Aug 31 '15 at 00:25
  • Also, can you expand on "the correct way", surely writing the same handing code again and again and again for each task could not be "the correct way"? Thank you for looking into this! – Andrew Savinykh Aug 31 '15 at 00:28
  • @zespri you can use this: http://stackoverflow.com/a/4257387/885318 to try and force a GC. But I'm not sure how reliable it really is. If you just want an example just have a `while (true)` loop with a `Task.Run(() => {throw new Exception();})` inside. It will raise the event very quickly. – i3arnon Aug 31 '15 at 06:50
  • @zespri You don't need to write the same code again and again because each action requires its own exception handling. But if you always do the same (e.g. log the error and move on) then abstract it. Have an `ExceptionHandler.HandleException` and use it for each task you want to handle. Exception handling shouldn't be really any different in tasks than in regular code. – i3arnon Aug 31 '15 at 06:53
  • So basically we do not know what exactly triggers the event. Okay, the answer has been useful none the less, so I'm accepting it. – Andrew Savinykh Sep 03 '15 at 21:14
  • @zespri we know **exactly** what triggers the event. And that's a task with an unobserved exception being GCed. – i3arnon Sep 04 '15 at 05:59
1

Use async-await instead of directly using Task, the exception handling will be straightforward as below:

try
{
  // Asynchronous implementation.
  await Task.Delay(1000);
}
catch (Exception ex)
{
  // Handle exceptions.
}

Or, if you still need to use task then use continue with as described here: How to manage properly an exception in a Task with ContinueWith

Community
  • 1
  • 1
Deepak Bhatia
  • 1,090
  • 1
  • 8
  • 13
  • I need a global handler. On task-by-task basis it's trivial, but it's not a good approach. If you notice, both approaches that I mentioned in my questions (that do not work) are global. This is what I'm looking for. – Andrew Savinykh Aug 28 '15 at 01:55
  • You can't have a global handler for your async tasks, what you can do is create a factory method for creating your tasks and do the error handling in that factory method. – Deepak Bhatia Aug 28 '15 at 01:59
  • I think that I can. I just don't know how. Do you have any references to convince me? – Andrew Savinykh Aug 28 '15 at 02:04
  • You can also try AppDomain.CurrentDomain.UnhandledException, see its implementation here: https://mlichtenberg.wordpress.com/2011/09/19/catching-unhandled-exceptions-in-asp-net/ – Deepak Bhatia Aug 28 '15 at 02:22
  • It sounds like you have not read the question. I specifically addressed this approach. Also the article you linked is not for asp.net webapi, but for straight asp.net with all the dependencies on system.web and handlers/modules that webapi does not have. – Andrew Savinykh Aug 28 '15 at 02:26
  • Sorry about missing the second part of your question. The best I can think of is using factory method for creating tasks and handle exception there. But let's see if someone suggest better solution. – Deepak Bhatia Aug 28 '15 at 03:05