Reputation: 20709
Due to bugs in BackgroundService
I'm using Stephen Cleary's excellent workaround. It works, but I can't return a non-zero exit code upon failure.
A minimal working example (for use with the $ dotnet new worker
template):
MyBackgroundService.cs
public class MyBackgroundService : BackgroundService {
private readonly IHostApplicationLifetime _lifetime;
public MyBackgroundService(IHostApplicationLifetime lifetime) => _lifetime = lifetime;
protected override Task ExecuteAsync(CancellationToken stoppingToken) => Task.Run(async () => {
try {
while (!stoppingToken.IsCancellationRequested) {
await Task.Delay(1000);
if (Random.Shared.NextDouble() < 0.5) throw new Exception("RANDOM CRASH!");
}
}
catch {
throw; // I can hit a debug breakpoint here
}
finally {
_lifetime.StopApplication();
}
});
}
Program.cs
public class Program {
public static int Main(string[] args) {
try {
var builder = Host.CreateApplicationBuilder(args);
builder.Services.AddHostedService<MyBackgroundService>();
var host = builder.Build();
host.Run();
return 0;
}
catch (Exception) {
return 1; // <----- execution never arrives here
}
}
}
When the app crashes, I expect the exit code to be 1
, but it is actually the default of 0
. When I debug it never enters the catch block.
What could be the reason?
Upvotes: 1
Views: 1216
Reputation: 20709
The workarounds in the accepted answer are good. But I chose the lazy route:
MyBackgroundService.cs
public class MyBackgroundService : BackgroundService {
private readonly IHostApplicationLifetime _lifetime;
public MyBackgroundService(IHostApplicationLifetime lifetime) => _lifetime = lifetime;
protected override Task ExecuteAsync(CancellationToken stoppingToken) => Task.Run(async () => {
var exitCode = 0; // <---------
try {
while (!stoppingToken.IsCancellationRequested) {
await Task.Delay(1000);
if (Random.Shared.NextDouble() < 0.5) throw new Exception("RANDOM CRASH!");
}
}
catch {
exitCode = 1; // <---------
throw;
}
finally {
if (Environment.ExitCode == 0 && exitCode != 0) // <---------
Environment.ExitCode = exitCode; // <---------
_lifetime.StopApplication();
}
});
}
Program.cs
// ...
host.Run();
return Environment.ExitCode; // <---------
Upvotes: 1
Reputation: 456657
It's actually because the task returned from BackgroundService.ExecuteAsync
is ignored (as explained on my blog). So, even though you're shutting down the host, the exception is still ignored.
Hosted services don't have a way to report exceptions or exit codes. Your options are:
BackgroundService
-derived type that exposes its Task
and have your main loop resolve instances of your derived type and await those tasks. This will observe exceptions from the background services.Environment.ExitCode
if they fail, similar to this code on my blog. Then your Main
can return void
.Both of these approaches do an "end run" around the app lifetime because the .NET host wasn't designed to allow background service results. I use the second one myself since the same code works for both worker processes and SCM services.
Upvotes: 3