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.
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.
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 ).
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With