0

I am making some code changes for a restful API service, in particular making some notification events completely async, so that a response can be returned without the caller needing to wait for the notification to complete.

To clarify, I am putting a wrapper around existing methods, which previously would have fallen into a catch from the caller, to be run as Fire and Forget tasks. Therefore any exceptions that may be raised by that task will become unhandled.

Also, to clarify, exceptions should be rare, and it doesn't really matter if they happen; these are secondary to the purpose of the API, and if we do get any unhandled exceptions, it's not a big issue that the notification didn't occur. However, I want to be sure that it doesn't affect the overall health of the API in a negative way.

Consider this example:

// this is an example of the application before my change
public class MyService
{
  public int MyMethod()
  {
    try 
    {
      // do stuff

      // perform secondary notification
      NotificationManager nm = new NotificationManager();
      nm.DoNotification();

    }
    catch (Exception ex)
    {
      // log exception
    }
    return 1;
  }
}

public class NotificationManager()
{
  public void DoNotification()
  {
    // perform notification
  }
}

In this example, the response is forced to wait for the notification event to complete. And if there is an unhandled exception in the DoNotification() method, it will be caught by the catch in the MyMethod() method.

Now, consider if I change the code to something like this:

// this is an example of the application after my change
public class MyService
{
  public int MyMethod()
  {
    try 
    {
      // do stuff

      // perform secondary notification
      NotificationManager nm = new NotificationManager();

      Task.Run(() =>
      {
        nm.DoNotification();
      });
    }
    catch (Exception ex)
    {
      // log exception
    }
    return 1;
  }
}

public class NotificationManager()
{
  public void DoNotification()
  {
    // perform notification
  }
}

Obviously, if there is an unhandled exception in the DoNotification() method, it will no longer be caught since it has been threaded off.

Just to confirm what I said earlier, I don't particularly care about handling the exception if one is raised, since it is secondary action. However, what I do care about is if it has a negative consequence on the health of the application since in the past we have had code where unhandled exceptions have brought down the entire API. Obviously, in a Production environment, that would be really bad.

So, my question is, what would happen with an unhandled exception in the example above? Would it:

  1. Be swallowed up by framework, with no one having any knowledge of it occurring? (I would be OK with that), or
  2. Cause the entire API to crash? (I would not be OK with that)

Many thanks

Theodor Zoulias
  • 34,835
  • 7
  • 69
  • 104
user978426
  • 57
  • 1
  • 5
  • Why not throw an excepton from `DoNotification` and find out? – Visual Studio Jun 22 '23 at 02:20
  • Hey there. I have tried that when running locally, and Visual Studios stops and highlights the unhandled exception. However, that is local testing only, with a local IIS instance created by the IDE. I do not know if that represents the behavior that would occur in a production IIS instance, however. Thanks – user978426 Jun 22 '23 at 02:40
  • [FYI](https://learn.microsoft.com/en-us/dotnet/standard/parallel-programming/exception-handling-task-parallel-library#unobservedtaskexception-event) – ProgrammingLlama Jun 22 '23 at 03:19
  • There's no guarantee this "fire and forget" task will even run. If it runs, it may find the objects it uses were disposed. This isn't new, it was the same in ASP.NET Framework too, because the *scope* of any resources created during a request is the request itself. In ASP.NET Core you can use a background service. In ASP.NET Framework you had to [tell ASP.NET about your task](https://www.hanselman.com/blog/how-to-run-background-tasks-in-aspnet) – Panagiotis Kanavos Jun 23 '23 at 11:36
  • What's the scope of `NotificationManager` and `MyService`? If they're singleton then `NotificationManager` should be injected into `MyService` and `DoNotification` should handle its own exceptions – Panagiotis Kanavos Jun 23 '23 at 11:37

2 Answers2

0
  1. Be swallowed up by framework, with no one having any knowledge of it occurring? (I would be OK with that)

This. Once upon a time (.NET Framework 4.0) it was otherwise, with unobserved Task exceptions crashing the process. But that was a long time ago. From 2012 onwards an unobserved Task exception just raises the TaskScheduler.UnobservedTaskException, and nothing else. Be aware that this event is raised when the Task that holds the unobserved exception is garbage collected, which might happen a long time after the exception happened.

You should also be aware that fire-and-forget tasks are not guaranteed to run, which might be a more serious concern, depending on how important these notifications are. An interesting blog post about this from Stephen Cleary: Fire and Forget on ASP.NET.

Theodor Zoulias
  • 34,835
  • 7
  • 69
  • 104
-2

OK, I am still unsure of the actual behaviour, but I have implemented a quick fix.

From:

// perform secondary notification
NotificationManager nm = new NotificationManager();

Task.Run(() =>
{
  nm.DoNotification();
});

To:

// perform secondary notification
NotificationManager nm = new NotificationManager();

Task.Run(() =>
{
  try {
    nm.DoNotification();
  } catch { }
});
user978426
  • 57
  • 1
  • 5
  • As it’s currently written, your answer is unclear. Please [edit] to add additional details that will help others understand how this addresses the question asked. You can find more information on how to write good answers [in the help center](/help/how-to-answer). – Community Jun 23 '23 at 00:26
  • That's not a fix at all. The actual bug is still there - the `NotificationManager` may not even exist when this runs. – Panagiotis Kanavos Jun 23 '23 at 11:38