Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Hosted service not terminating after Environment.Exit

Tags:

.net-core

I've got a .NET core 3.1 app with a hosted service that runs as a console application on Windows.

In case of an error I'm trying to terminate the worker with Environment.Exit(1).

Now the problem is that, if Enviroment.Exit() is called before any await in ExecuteAsync, the application does not terminate. It logs Waiting for the host to be disposed. Ensure all 'IHost' instances are wrapped in 'using' blocks. and then hangs indefinitely.
When I await anything before the call to Enviroment.Exit() it also logs that, but it terminates as expected.

Here is the simplest code that I could come up with to reproduce the problem.
The NotTerminatingWorker hangs forever, the TerminatingWorker terminates. The only difference is a tiny Task.Delay:


  public class Program {
    public static async Task Main(string[] args) {
      using var host = CreateHostBuilder(args).Build();
      await host.RunAsync();
    }

    public static IHostBuilder CreateHostBuilder(string[] args) {
      return Host.CreateDefaultBuilder(args)
        .ConfigureServices((hostContext, services) => { services.AddHostedService<NotTerminatingWorker>(); });
    }
  }


  public class NotTerminatingWorker : BackgroundService {
    protected override async Task ExecuteAsync(CancellationToken stoppingToken) {
      Environment.Exit(1);
    }
  }

  public class TerminatingWorker : BackgroundService {
    protected override async Task ExecuteAsync(CancellationToken stoppingToken) {
      await Task.Delay(1);
      Environment.Exit(1);
    }
  }

I would expect that both behave the same way, but that's obviously not the case.
Any explanation for this would be greatly appreciated!

UPDATE: The application should be able to run both as a console application and as a Windows service. The non-zero return code is required to get it restarted if it crashes. And apparently Windows does not restart services that exited with code 0.

like image 766
Michael Sandino Avatar asked Jan 23 '20 13:01

Michael Sandino


People also ask

What does environment exit do?

Exit terminates an application immediately, even if other threads are running. If the return statement is called in the application entry point, it causes an application to terminate only after all foreground threads have terminated. Exit requires the caller to have permission to call unmanaged code.

When should I use IHostedService?

The IHostedService interface provides a convenient way to start background tasks in an ASP.NET Core web application (in . NET Core 2.0 and later versions) or in any process/host (starting in . NET Core 2.1 with IHost ).


1 Answers

I believe the behavior you're seeing is a side-effect of how the .NET Core runtime does its startup: it calls ExecuteAsync for each background worker and then waits for it to complete. So a synchronous ExecuteAsync can cause problems. I've used Task.Run to work around this.

In case of an error I'm trying to terminate the worker with Environment.Exit(1).

I recommend not using Environment.Exit at all. Instead, do a controlled shutdown by injecting IHostApplicationLifetime and calling StopApplication. This will trigger the stoppingToken for each of your background services, and if they ignore it, they will be forcibly terminated after a timeout.

like image 157
Stephen Cleary Avatar answered Oct 18 '22 22:10

Stephen Cleary