Home > OS >  .NET 6 Difficulty understanding the new hosting exception handling behaviors
.NET 6 Difficulty understanding the new hosting exception handling behaviors

Time:07-29

I've been reading through the MS documentation regarding the deployment of a Windows service to run a Worker App. Doc ref --> enter image description here

There are a few concepts that are not clear to me and hoping someone can advise:

In my own project, my background services include various classes and operations such as connection managemenet for Azure IOT Hubs Device Client. In some cases, i simply do no want to force the environment i.e. the whole app to exit on every catch/exception scenario, but the docs are not clear on whether we are expceted to do this? I mean why catch an exception if we're going to simply wipe out the running of the application every time? doesnt make sense to me...

The next point ref the following statement "To correctly allow the service to be restarted, you can call Environment.Exit with a non-zero exit code" but then earlier in the article, it also talks about the two options available for 'BackgroundServiceExceptionBehavior':

  • Ignore - Ignore exceptions thrown in BackgroundService. StopHost
  • The IHost will be stopped when an unhandled exception is thrown.

An unhandled exception in my mind means the app has gone fowl on something that hasnt been appropriately caught in the right place i.e. where no try/catch block exists. So how does one provision an 'Environment.Exit(1)' to something they havent yet accounted for? And what happens in thie scenario?

The way the article reads to me suggests that the only way we can ensure the Windows Service will manage the re-starting of the app succesfully is from any exception that we knowingly caught, but that equally that doesnt tie up with what the general article is suggesting will happen.

Totally confused :(

CodePudding user response:

As described in the article pre .NET 6 an unhandled exception in background service has not affected application in any way - for example if you had an app which only job was to process some queue in background service and this service would fail then the app has continued running as if nothing happened, which obviously is wrong in such cases. That's why it was fixed in .NET 6.

As service recovery options and .NET BackgroundService instances paragraph states new default behavior for unhandled exceptions is StopHost which behaves like as if application (Windows service) would exit normally (with exit code 0):

But it stops cleanly, meaning that the Windows Service management system will not restart the service

If you want your app to be restarted automatically by the Windows Service management system (if it is setup to do so) you need to "handle" the exception and end the application with non-zero exit code which indicates failure.

Obviously if some concrete exception is recoverable you need to just recover:

while (!stoppingToken.IsCancellationRequested)
{
    try
    {
        // some job
    }
    catch (SomeRecoverableException e)
    {
        // handle it
    }
    catch (Exception ex)
    {
        _logger.LogError(ex, "{Message}", ex.Message);
        Environment.Exit(1);
    }
}

The Ignore option exists for backward compatibility so it's possible to revert to the previous behaviour (can't think off the top of my head why it is possibly can be needed but still).

CodePudding user response:

A .NET BackgroundService is a platform-agnostic construct to represent a short- or long-running tasks in a .NET application.

You can have a background service in an ASP.NET application, as well as in a WinForms application or a plain old console application.

These background services have no connection to the Windows-specific thing that are Windows Services.

You can have a Windows Service that does absolutely nothing, or one that runs a custom task of yours, or one that runs one or more BackgroundServices. Or a combination of the latter. You can have one application that hosts multiple Windows Services, but let's ignore that.

Now what do you want your service to do when it starts with zero background services? It should probably continue doing whatever else it was supposed to do. What if the Windows Service runs multiple BackgroundServices and one stops? Probably continue. What if one throws an exception?

That's probably not good, and if your process does other things besides hosting that one now crashed service, you don't want those jobs to continue. So since .NET 6 the runtime pulls the rug from under the entire application, so everything that service was doing stops happening.

That's where this lifetime change comes in, and setting the process exit code and exiting the application from your exception handler is one way to let the Service Control Manager know your service crashed. But then you'll have to configure your service to treat nonzero exit codes as failures, as the regular way to report errors to the SCM is by reporting them to the SCM, which Application.Exit() doesn't. You also can't combine ServiceBase.ExitCode and the process's exit code, because the former is only reported to the SCM when you call Stop(), and then the SCM will think your service exited nicely (because you told it you were stopping).

There are more issues with the .NET Platform Extensions (which contain the Windows Service helper classes) involving error handing, so I've written a library to solve them: https://github.com/CodeCasterNL/WindowsServiceExtensions.

  • Related