Host Genérico .NET
Neste artigo, você aprenderá sobre os vários padrões para configuração e criação de um host genérico .NET disponível no pacote NuGet Microsoft.Extensions.Hosting. O Host Genérico do .NET é responsável pela inicialização e gerenciamento do tempo de vida do aplicativo. Os modelos do Serviço de Trabalho criam um host genérico .NET. HostApplicationBuilder O Host Genérico pode ser usado com outros tipos de aplicativos .NET, como aplicativos de console.
Um host é um objeto que encapsula os recursos e a funcionalidade de tempo de vida de um aplicativo, tais como:
- DI (injeção de dependência)
- Log
- Configuração
- Desligamento do aplicativo
- Implementações de
IHostedService
Quando um host é iniciado, ele chama IHostedService.StartAsync em cada implementação de IHostedService registrada na coleção de serviços hospedados do contêiner de serviço. Em um aplicativo de serviço de trabalho, todas as implementações IHostedService
que contêm instâncias BackgroundService têm seus métodos BackgroundService.ExecuteAsync chamados.
O principal motivo para incluir todos os recursos interdependentes do aplicativo em um objeto é o gerenciamento de tempo de vida: controle sobre a inicialização do aplicativo e desligamento normal.
Configurar um host
O host normalmente é configurado, compilado e executado pelo código na classe Program
. O método Main
:
- Chama um método CreateApplicationBuilder para criar e configurar um objeto construtor.
- Chama Build() para criar uma instância IHost.
- Chama Run ou o método RunAsync no objeto host.
Os modelos do Serviço de Trabalho do .NET geram o seguinte código para criar um Host Genérico:
using Example.WorkerService;
HostApplicationBuilder builder = Host.CreateApplicationBuilder(args);
builder.Services.AddHostedService<Worker>();
IHost host = builder.Build();
host.Run();
Para obter mais informações sobre as Funções de Trabalho, confira Serviços de Trabalho no .NET.
Configurações do construtor de host
O método CreateApplicationBuilder:
- Define a raiz do conteúdo como o caminho retornado por GetCurrentDirectory().
- Carrega a configuração do host de:
- Variáveis de ambiente prefixadas com
DOTNET_
. - Argumentos de linha de comando.
- Variáveis de ambiente prefixadas com
- Carrega a configuração do aplicativo de:
- appsettings.json.
- appsettings.{Environment}.json.
- Gerenciador de Segredo quando o aplicativo é executado no ambiente
Development
. - Variáveis de ambiente.
- Argumentos de linha de comando.
- Adiciona os seguintes provedores de registro em log:
- Console
- Depurar
- EventSource
- EventLog (somente quando em execução no Windows)
- Habilita a validação de escopo e a validação de dependência quando o ambiente é
Development
.
HostApplicationBuilder.Services é uma instância de Microsoft.Extensions.DependencyInjection.IServiceCollection. Esses serviços são usados para criar um IServiceProvider usado com a injeção de dependência para resolver os serviços registrados.
Serviços fornecidos pela estrutura
Ao chamar IHostBuilder.Build() ou HostApplicationBuilder.Build(), os seguintes serviços são registrados automaticamente:
Construtores de host baseados em cenários adicionais
Se você estiver criando para a Web ou escrevendo um aplicativo distribuído, talvez seja necessário usar um construtor de host diferente. Considere a seguinte lista de construtores de host adicionais:
- DistributedApplicationBuilder: um construtor para criar aplicativos distribuídos. Para obter mais informações, confira .NET Aspire.
- WebApplicationBuilder: um construtor para aplicativos e serviços Web. Para obter mais informações, confira ASP.NET Core.
- WebHostBuilder: um construtor para
IWebHost
. Para obter mais informações, confira Host da Web do AsP.NET Core.
IHostApplicationLifetime
Injete o serviço IHostApplicationLifetime em qualquer classe para lidar com tarefas de pós-inicialização e de desligamento normal. Três propriedades na interface são tokens de cancelamento usados para registrar métodos de manipulador de eventos de inicialização e desligamento do aplicativo. A interface também inclui um método StopApplication().
O exemplo a seguir é uma implementação IHostedService e IHostedLifecycleService que registra eventos IHostApplicationLifetime
:
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
namespace AppLifetime.Example;
public sealed class ExampleHostedService : IHostedService, IHostedLifecycleService
{
private readonly ILogger _logger;
public ExampleHostedService(
ILogger<ExampleHostedService> logger,
IHostApplicationLifetime appLifetime)
{
_logger = logger;
appLifetime.ApplicationStarted.Register(OnStarted);
appLifetime.ApplicationStopping.Register(OnStopping);
appLifetime.ApplicationStopped.Register(OnStopped);
}
Task IHostedLifecycleService.StartingAsync(CancellationToken cancellationToken)
{
_logger.LogInformation("1. StartingAsync has been called.");
return Task.CompletedTask;
}
Task IHostedService.StartAsync(CancellationToken cancellationToken)
{
_logger.LogInformation("2. StartAsync has been called.");
return Task.CompletedTask;
}
Task IHostedLifecycleService.StartedAsync(CancellationToken cancellationToken)
{
_logger.LogInformation("3. StartedAsync has been called.");
return Task.CompletedTask;
}
private void OnStarted()
{
_logger.LogInformation("4. OnStarted has been called.");
}
private void OnStopping()
{
_logger.LogInformation("5. OnStopping has been called.");
}
Task IHostedLifecycleService.StoppingAsync(CancellationToken cancellationToken)
{
_logger.LogInformation("6. StoppingAsync has been called.");
return Task.CompletedTask;
}
Task IHostedService.StopAsync(CancellationToken cancellationToken)
{
_logger.LogInformation("7. StopAsync has been called.");
return Task.CompletedTask;
}
Task IHostedLifecycleService.StoppedAsync(CancellationToken cancellationToken)
{
_logger.LogInformation("8. StoppedAsync has been called.");
return Task.CompletedTask;
}
private void OnStopped()
{
_logger.LogInformation("9. OnStopped has been called.");
}
}
O modelo do Serviço de Trabalho pode ser modificado para adicionar a implementação ExampleHostedService
:
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using AppLifetime.Example;
HostApplicationBuilder builder = Host.CreateApplicationBuilder(args);
builder.Services.AddHostedService<ExampleHostedService>();
using IHost host = builder.Build();
await host.RunAsync();
O aplicativo gravaria a seguinte saída de exemplo:
// Sample output:
// info: AppLifetime.Example.ExampleHostedService[0]
// 1.StartingAsync has been called.
// info: AppLifetime.Example.ExampleHostedService[0]
// 2.StartAsync has been called.
// info: AppLifetime.Example.ExampleHostedService[0]
// 3.StartedAsync has been called.
// info: AppLifetime.Example.ExampleHostedService[0]
// 4.OnStarted has been called.
// info: Microsoft.Hosting.Lifetime[0]
// Application started. Press Ctrl+C to shut down.
// info: Microsoft.Hosting.Lifetime[0]
// Hosting environment: Production
// info: Microsoft.Hosting.Lifetime[0]
// Content root path: ..\app-lifetime\bin\Debug\net8.0
// info: AppLifetime.Example.ExampleHostedService[0]
// 5.OnStopping has been called.
// info: Microsoft.Hosting.Lifetime[0]
// Application is shutting down...
// info: AppLifetime.Example.ExampleHostedService[0]
// 6.StoppingAsync has been called.
// info: AppLifetime.Example.ExampleHostedService[0]
// 7.StopAsync has been called.
// info: AppLifetime.Example.ExampleHostedService[0]
// 8.StoppedAsync has been called.
// info: AppLifetime.Example.ExampleHostedService[0]
// 9.OnStopped has been called.
A saída mostra a ordem de todos os vários eventos de ciclo de vida:
IHostedLifecycleService.StartingAsync
IHostedService.StartAsync
IHostedLifecycleService.StartedAsync
IHostApplicationLifetime.ApplicationStarted
Quando o aplicativo é interrompido, por exemplo, com Ctrl+C, os seguintes eventos são gerados:
IHostApplicationLifetime.ApplicationStopping
IHostedLifecycleService.StoppingAsync
IHostedService.StopAsync
IHostedLifecycleService.StoppedAsync
IHostApplicationLifetime.ApplicationStopped
IHostLifetime
A implementação IHostLifetime controla quando o host é iniciado e quando ele é interrompido. A última implementação registrada é usada. Microsoft.Extensions.Hosting.Internal.ConsoleLifetime
é a implementação IHostLifetime
padrão. Para obter mais informações sobre a mecânica de tempo de vida do desligamento, consulte Desligamento do host.
A interface IHostLifetime
expõe um método IHostLifetime.WaitForStartAsync, que é chamado no início de IHost.StartAsync
que aguardará até que ele seja concluído antes de continuar. Isso pode ser usado para atrasar a inicialização até que seja sinalizado por um evento externo.
Além disso, a interface IHostLifetime
expõe um método IHostLifetime.StopAsync, que é chamado de IHost.StopAsync
para indicar que o host está parando e é hora de desligar.
IHostEnvironment
Injete o serviço IHostEnvironment em uma classe para obter informações sobre as seguintes configurações:
- IHostEnvironment.ApplicationName
- IHostEnvironment.ContentRootFileProvider
- IHostEnvironment.ContentRootPath
- IHostEnvironment.EnvironmentName
Além disso, o serviço IHostEnvironment
expõe a capacidade de avaliar o ambiente com a ajuda desses métodos de extensão:
- HostingEnvironmentExtensions.IsDevelopment
- HostingEnvironmentExtensions.IsEnvironment
- HostingEnvironmentExtensions.IsProduction
- HostingEnvironmentExtensions.IsStaging
Configuração do host
A configuração do host é usada para configurar propriedades da implementação IHostEnvironment.
A configuração do host está disponível na propriedade HostApplicationBuilderSettings.Configuration e a implementação do ambiente está disponível na propriedade IHostApplicationBuilder.Environment. Para configurar o host, acesse a propriedade Configuration
e chame qualquer um dos métodos de extensão disponíveis.
Para adicionar a configuração do host, considere o seguinte exemplo:
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Hosting;
HostApplicationBuilderSettings settings = new()
{
Args = args,
Configuration = new ConfigurationManager(),
ContentRootPath = Directory.GetCurrentDirectory(),
};
settings.Configuration.AddJsonFile("hostsettings.json", optional: true);
settings.Configuration.AddEnvironmentVariables(prefix: "PREFIX_");
settings.Configuration.AddCommandLine(args);
HostApplicationBuilder builder = Host.CreateApplicationBuilder(settings);
using IHost host = builder.Build();
// Application code should start here.
await host.RunAsync();
O código anterior:
- Define a raiz do conteúdo como o caminho retornado por GetCurrentDirectory().
- Carrega a configuração do host de:
- hostsettings.json.
- Variáveis de ambiente prefixadas com
PREFIX_
. - Argumentos de linha de comando.
Configuração do aplicativo
A configuração do aplicativo é criada chamando ConfigureAppConfiguration em um IHostApplicationBuilder. A propriedade públicaIHostApplicationBuilder.Configuration permite que os consumidores leiam ou façam alterações na configuração existente usando métodos de extensão disponíveis.
Para obter mais informações, confira Configuração no .NET.
Desligamento do host
Há várias maneiras como um processo hospedado é interrompido. O mais comum é que um processo de serviço hospedado possa ser interrompido das seguintes maneiras:
- Se alguém não chamar Run ou HostingAbstractionsHostExtensions.WaitForShutdown o aplicativo sair normalmente com a conclusão de
Main
. - Se o aplicativo falhar.
- Se o aplicativo for desligado à força usando SIGKILL (ou CTRL+Z).
O código de hospedagem não é responsável por lidar com esses cenários. O proprietário do processo precisa lidar com eles da mesma forma que qualquer outro aplicativo. Há várias outras maneiras como um processo de serviço hospedado pode ser interrompido:
- Se
ConsoleLifetime
for usado (UseConsoleLifetime), ele escutará os sinais a seguir e tentará interromper o host normalmente. - Se o aplicativo chamar Environment.Exit.
A lógica de hospedagem interna lida com esses cenários, especificamente a classe ConsoleLifetime
. ConsoleLifetime
tenta lidar com os sinais de "desligamento" SIGINT, SIGQUIT e SIGTERM para permitir uma saída normal para o aplicativo.
Antes do .NET 6, não havia uma maneira de o código .NET manipular normalmente o SIGTERM. Para contornar essa limitação, ConsoleLifetime
assinaria para System.AppDomain.ProcessExit. Quando ProcessExit
era acionado, ConsoleLifetime
sinalizaria que o host parasse e bloqueasse o thread ProcessExit
, aguardando o host parar.
O manipulador de saída do processo permitiria que o código de limpeza no aplicativo fosse executado , por exemplo, IHost.StopAsync e código depois de HostingAbstractionsHostExtensions.Run no método Main
.
No entanto, havia outros problemas com essa abordagem porque SIGTERM não era a única maneira de gerar ProcessExit
. SIGTERM também é gerado quando o código do aplicativo chama Environment.Exit
. Environment.Exit
não é uma maneira normal de desligar um processo no modelo de aplicativo Microsoft.Extensions.Hosting
. Ele aciona o evento ProcessExit
e, em seguida, sai do processo. O final do método Main
não é executado. Os threads em segundo e primeiro plano são encerrados e os blocosfinally
não são executados.
Como ConsoleLifetime
bloqueava ProcessExit
enquanto esperava o host ser desligado, esse comportamento fazia com que deadlocks de Environment.Exit
também bloqueassem a espera pela chamada ProcessExit
. Além disso, como a manipulação do SIGTERM estava tentando desligar o processo normalmente, ConsoleLifetime
definia ExitCode para 0
, o que sobrecarregava o código de saída do usuário passado para Environment.Exit
.
No .NET 6, os sinais POSIX têm suporte e são manipulados. O ConsoleLifetime
manipula SIGTERM normalmente e não se envolve mais quando Environment.Exit
é invocado.
Dica
No .NET 6+, ConsoleLifetime
não tem mais lógica para lidar com o cenário Environment.Exit
. Os aplicativos que chamam Environment.Exit
e precisam executar a lógica de limpeza podem se inscrever em ProcessExit
sozinhos. A hospedagem não tentará mais interromper normalmente o host nesses cenários.
Se o aplicativo usa hospedagem e você deseja interromper normalmente o host, você pode chamar IHostApplicationLifetime.StopApplication em vez de Environment.Exit
.
Processo de desligamento de hospedagem
O diagrama de sequência a seguir mostra como os sinais são tratados internamente no código de hospedagem. A maioria dos usuários não precisa entender esse processo. Mas para desenvolvedores que precisam de uma compreensão profunda, um bom visual pode ajudá-los a começar.
Depois que o host for iniciado, quando um usuário chamar Run
ou WaitForShutdown
, um manipulador se registra em IApplicationLifetime.ApplicationStopping. A execução está pausada em WaitForShutdown
, aguardando o evento ApplicationStopping
ser acionado. O método Main
não retorna imediatamente e o aplicativo permanece em execução até Run
ou WaitForShutdown
retorna.
Quando um sinal é enviado para o processo, ele inicia a seguinte sequência:
- O controle flui de
ConsoleLifetime
paraApplicationLifetime
para acionar o eventoApplicationStopping
. Isso sinalizaWaitForShutdownAsync
para desbloquear o código de execuçãoMain
. Enquanto isso, o manipulador de sinal POSIX retornaCancel = true
desde que esse sinal POSIX tenha sido tratado. - O código de execução
Main
começa a ser executado novamente e informa ao host paraStopAsync()
, o qual, por sua vez, interrompe todos os serviços hospedados e gera quaisquer outros eventos parados. - Por fim,
WaitForShutdown
sai, permitindo que qualquer código de limpeza de aplicativo seja executado e para que o métodoMain
saia normalmente.
Desligamento de host em cenários de servidor Web
Há vários outros cenários comuns em que o desligamento normal funciona no Kestrel para os protocolos HTTP/1.1 e HTTP/2, e como você pode configurá-lo em ambientes diferentes com um balanceador de carga para esvaziar o tráfego sem problemas. Embora a configuração do servidor Web esteja além do escopo deste artigo, você pode encontrar mais informações na documentação Configurar opções para o servidor Web ASP.NET Core Kestrel.
Quando o Host recebe um sinal de desligamento (por exemplo, CTL+C ou StopAsync
), ele notifica o aplicativo sinalizando ApplicationStopping. Você deverá assinar esse evento se tiver operações de execução longa que precisem ser concluídas normalmente.
Em seguida, o Host chama IServer.StopAsync com um tempo limite de desligamento que você pode configurar (30s por padrão). Kestrel (e Http.Sys) fecham suas associações de porta e param de aceitar novas conexões. Eles também informam as conexões atuais para interromper o processamento de novas solicitações. Para HTTP/2 e HTTP/3, uma mensagem GOAWAY
preliminar é enviada ao cliente. Para HTTP/1.1, eles interrompem o loop de conexão porque as solicitações são processadas em ordem. O IIS se comporta de modo diferente, rejeitando novas solicitações com um código de status 503.
As solicitações ativas têm até o tempo limite de desligamento para serem concluídas. Se todas foram concluídas antes do tempo limite, o servidor retornará o controle para o host mais cedo. Se o tempo limite expirar, as conexões e solicitações pendentes serão anuladas forçadamente, o que pode causar erros nos logs e nos clientes.
Considerações do Balanceador de carga
Para garantir uma transição suave de clientes para um novo destino ao trabalhar com um balanceador de carga, você pode seguir estas etapas:
- Abra a nova instância e comece a balancear o tráfego para ela (talvez você já tenha várias instâncias para fins de dimensionamento).
- Desabilite ou remova a instância antiga na configuração do balanceador de carga para que ela pare de receber novo tráfego.
- Sinalize a instância antiga para desligar.
- Aguarde até ela esvaziar ou atingir o tempo limite.
Confira também
- Injeção de dependência no .NET
- Registro em log no .NET
- Configuração no .NET
- Serviços de Trabalho no .NET
- Host da Web do ASP.NET Core
- Configuração do servidor Web ASP.NET Core Kestrel
- Os bugs de host genéricos devem ser criados no repositório github.com/dotnet/runtime