Devido a bugs, estou usando a excelente solução alternativaBackgroundService
de Stephen Cleary . Funciona, mas não consigo retornar um código de saída diferente de zero em caso de falha.
Um exemplo mínimo de trabalho (para uso com o $ dotnet new worker
modelo):
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
}
}
}
Quando o aplicativo trava, espero que o código de saída seja 1
, mas na verdade é o padrão 0
. Quando eu depuro, ele nunca entra no bloco catch.
Qual seria a razão?
Na verdade, é porque a tarefa retornada
BackgroundService.ExecuteAsync
é ignorada (conforme explicado no meu blog). Portanto, mesmo que você desligue o host, a exceção ainda será ignorada.Os serviços hospedados não têm como relatar exceções ou códigos de saída. Suas opções são:
BackgroundService
tipo derivado que o expõeTask
e faça com que seu loop principal resolva instâncias de seu tipo derivado e aguarde essas tarefas. Isso observará exceções dos serviços em segundo plano.Environment.ExitCode
caso eles falhem, semelhante a este código no meu blog . Então vocêMain
pode retornarvoid
.Ambas as abordagens realizam uma "execução final" durante a vida útil do aplicativo porque o host .NET não foi projetado para permitir resultados de serviço em segundo plano. Eu mesmo uso o segundo, pois o mesmo código funciona tanto para processos de trabalho quanto para serviços SCM.
As soluções alternativas na resposta aceita são boas. Mas escolhi o caminho preguiçoso:
MyBackgroundService.cs
Program.cs