.NET Generic Host : Prevent application crash on unhandled exception
Asked Answered
M

3

10

I have a .NET Generic Host with an IHostedService that receives events from an OPC-UA interface and that process them.

The problem is that if an unhandled exception occurs during the processing of an event, the whole application crashes

I read the documentation but I didn't find anything about a global exception handler or a similar mechanism that allows to catch unhandled exception and prevent an application crash.

Is there a solution to protect the Generic Host, as well as the IHostedService against unhandled exceptions ?

EDIT

I know, here the simplest thing is to try/catch the exception and not let it bubble up. But I'd like to know if there is a similar mechanism like in WinForms / WPF, where you can catch such exceptions more globally and prevent a crash.

EDIT

This is a simplified version of the code:

public class Program
{
    public static void Main(string[] args)
    {
        CreateHostBuilder(args).Build().Run();
    }

    public static IHostBuilder CreateHostBuilder(string[] args)
    {
        var environmentName = Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT");
        return new HostBuilder()
            .UseEnvironment(environmentName)
            .ConfigureLogging((hostContext, logging) =>
            {
                ...
            })
            .ConfigureAppConfiguration((hostContext, builder) =>
            {
                builder
                    .SetBasePath(Directory.GetCurrentDirectory())
                    .AddJsonFile("appsettings.json", true, true)
                    .AddJsonFile($"appsettings.{hostContext.HostingEnvironment.EnvironmentName}.json", true, true)
                    .AddEnvironmentVariables();
            })
            .ConfigureServices((hostContext, services) =>
            {
                services.AddSingleton<IHostedService, OpcClientHostedService>();
                ...
            });
    }
}

public class OpcClientHostedService : IHostedService
{
    private readonly OpcConfiguration _opcConfiguration;
    private readonly ILogger _logger;
    private readonly OpcClient _opcClient;

    public OpcClientHostedService(OpcConfiguration opcConfiguration, ILogger<OpcClientHostedService> logger)
    {
        _opcConfiguration = opcConfiguration;
        _logger = logger;
        _opcClient = new OpcClient(opcConfiguration.ServerUri);
    }

    public Task StartAsync(CancellationToken cancellationToken)
    {
        _logger.LogInformation("Connecting to OPC server with URI '{0}'", _opcConfiguration.ServerUri);
        try
        {
            _opcClient.Connect();
        }
        catch (Exception ex)
        {
            _logger.LogCritical(ex, "Cannot connect to OPC server");
            throw;
        }
        _logger.LogInformation("Connection to OPC server is successful");

        CreateOpcObservable().Subscribe(async args => await ProcessOpcEvent(args));

        return Task.CompletedTask;
    }

    private async Task ProcessOpcEvent(OpcValueChangedEventArgs args)
    {
        await MethodThatCanThrowAnException(args); // If the exception is not handled, the whole application crashes!
    }

    public Task StopAsync(CancellationToken cancellationToken)
    {
        _logger.LogInformation("Disconnecting to OPC server");
        _opcClient.Disconnect();
        _logger.LogInformation("Disconnection to OPC server is successful");
        return Task.CompletedTask;
    }

    ...
}

In this example, it is easy to add a try/catch block in the ProcessOpcEvent method, but it would be great to have a mechanism to avoid complete application crash in this kind of situation.

Mouse answered 29/8, 2018 at 16:5 Comment(11)
You may want to look at this post (#5763026)Norland
show some code. otherwise you're going to get theoretical statements.Kimberlite
@RyanWilson unfortunately this post is not helpful as my application is not a WinForms application, but a .Net core console applicationMouse
What does the code look like?Beware
@Beware I added some simplified code, but I think it contains all relevant partsMouse
You could create an abstract class implementing IHostedService and have your services extend from that class instead of implementing the interface directly. Write your error handling in the abstract class, and provide a virtual / abstract method for your sub classes to implement instead of the normal StartAsync, etc.Whipperin
Also, why are you subscribing to CreateOpcObservable() and then throwing away the subscription/disposable? It seems like you should keep the subscription around so you can dispose of it if/when necessary.Whipperin
@Whipperin in the real code I keep the referenceMouse
@Mouse I don't understand what that means or how it applies to my suggestion...Whipperin
Yea you just need to try catchBeware
“[is there] a similar mechanism like in WinForms / WPF” – Yes and no: Exceptions are automatically caught inside of the request pipeline, so your application stays alive even though individual requests may fail. But the exceptions that hit at the Program.cs level are those, that the application cannot recover from automatically. So you will have to handle those. If your background service fails, then how should the application possibly handle that? You will have to make sure that the background service runs without issues (or if there are issues, it has to handle them internally).Footmark
K
5

You can add an app domain level event handler but you won't be able to control execution continuing in any specific manner. In order to be able to control execution and how it handles exceptions you MUST implement try/catch statements when you process your OPC payload messages.

Kimberlite answered 29/8, 2018 at 16:11 Comment(2)
This is the answer I feared :( Adding try/catch statements is not a big deal but it would be good to have some kind of safety net.Mouse
Try catch statements are simpleKimberlite
C
4

You can provide custom IHostBuilder and IHost implementations that wrap the originals and catch errors.

See this gist sample that demonstrates the approach and that logs global errors.

Caz answered 29/6, 2021 at 9:30 Comment(0)
A
0

According to this: the behaviour has changed in .NET 6

In previous versions, when a BackgroundService throws an unhandled exception, the exception is lost and the service appears unresponsive. .NET 6 fixes this behavior by logging the exception and stopping the host.

https://learn.microsoft.com/en-us/dotnet/core/compatibility/core-libraries/6.0/hosting-exception-handling

Adoree answered 1/6 at 21:44 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.