Host Genérico .NET
Neste artigo, você aprenderá sobre os vários padrões para configurar e criar um Host Genérico .NET disponível no pacote NuGet Microsoft.Extensions.Hosting . O Host Genérico .NET é responsável pela inicialização do aplicativo e pelo gerenciamento do tempo de vida. 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, como:
- Injeção de dependência (DI)
- Registo
- Configuração
- Desligamento do aplicativo
IHostedService
Implementações
Quando um host é iniciado, ele chama IHostedService.StartAsync cada implementação de IHostedService registrado na coleção de serviços hospedados do contêiner de serviço. Em um aplicativo de serviço de trabalho, todas as IHostedService
implementações que contêm BackgroundService instâncias têm seus BackgroundService.ExecuteAsync métodos chamados.
A principal razão para incluir todos os recursos interdependentes do aplicativo em um objeto é o gerenciamento do tempo de vida: controle sobre a inicialização do aplicativo e desligamento normal.
Configurar um anfitrião
O host é normalmente configurado, criado e executado por código na Program
classe. O Main
método:
- Chama um CreateApplicationBuilder método para criar e configurar um objeto construtor.
- Chamadas Build() para criar uma IHost instância.
- Chamadas Run ou RunAsync método no objeto host.
Os modelos do .NET Worker Service 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 os Serviços do Trabalhador, consulte Serviços do Trabalhador no .NET.
Configurações do construtor de hosts
O CreateApplicationBuilder método:
- Define a raiz do conteúdo como o caminho retornado pelo 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. {Ambiente}.json.
- Secret Manager quando o aplicativo é executado no
Development
ambiente. - Variáveis de ambiente.
- Argumentos de linha de comando.
- Adiciona os seguintes provedores de log:
- Consola
- Depurar
- Fonte de eventos
- EventLog (somente quando executado no Windows)
- Permite a validação de escopo e a validação de dependência quando o ambiente é
Development
.
O HostApplicationBuilder.Services é um Microsoft.Extensions.DependencyInjection.IServiceCollection exemplo. Esses serviços são usados para criar um IServiceProvider que é usado com injeção de dependência para resolver os serviços registrados.
Serviços prestados no quadro
Quando você liga para um IHostBuilder.Build() ou HostApplicationBuilder.Build(), os seguintes serviços são registrados automaticamente:
Construtores de hosts adicionais baseados em cenários
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, consulte .NET Aspire.
- WebApplicationBuilder: Um construtor de aplicações e serviços Web. Para obter mais informações, consulte ASP.NET Core.
- WebHostBuilder: Um construtor para
IWebHost
. Para obter mais informações, consulte ASP.NET host principal.
IHostApplicationLifetime
Injete o IHostApplicationLifetime serviço em qualquer classe para lidar com tarefas 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 início de aplicativo e parada de aplicativo. A interface também inclui um StopApplication() método.
O exemplo a seguir é uma IHostedService implementação e IHostedLifecycleService que registra IHostApplicationLifetime
eventos:
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 Serviço de Trabalho pode ser modificado para adicionar a ExampleHostedService
implementação:
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 do 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 IHostLifetime implementação controla quando o host é iniciado e quando ele para. É utilizada a última implementação registada. Microsoft.Extensions.Hosting.Internal.ConsoleLifetime
é a implementação padrão IHostLifetime
. Para obter mais informações sobre a mecânica de tempo de vida do desligamento, consulte Desligamento do host.
A IHostLifetime
interface expõe um IHostLifetime.WaitForStartAsync método, que é chamado no início do qual irá esperar até que esteja completo antes de IHost.StartAsync
continuar. Isso pode ser usado para atrasar a inicialização até ser sinalizado por um evento externo.
Além disso, a IHostLifetime
interface expõe um IHostLifetime.StopAsync método, que é chamado de para indicar que o host está parando e é hora de IHost.StopAsync
desligar.
IHostEnvironment
Injete o IHostEnvironment serviço em uma classe para obter informações sobre as seguintes configurações:
- IHostEnvironment.ApplicationName
- IHostEnvironment.ContentRootFileProvider
- IHostEnvironment.ContentRootPath
- IHostEnvironment.EnvironmentName
Além disso, o IHostEnvironment
serviço expõe a capacidade de avaliar o ambiente com a ajuda destes 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 as propriedades da implementação IHostEnvironment .
A configuração do host está disponível na HostApplicationBuilderSettings.Configuration propriedade e a implementação do ambiente está disponível na IHostApplicationBuilder.Environment propriedade. Para configurar o host, acesse a Configuration
propriedade 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 pelo 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 um IHostApplicationBuilderarquivo . 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, consulte Configuração no .NET.
Desligamento do host
Há várias maneiras pelas quais um processo hospedado é interrompido. Mais comumente, um processo hospedado pode ser interrompido das seguintes maneiras:
- Se alguém não ligar Run ou HostingAbstractionsHostExtensions.WaitForShutdown e o aplicativo sair normalmente com
Main
a conclusão. - 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 pelas quais um processo de serviço hospedado pode ser interrompido:
- Se
ConsoleLifetime
for usado (UseConsoleLifetime), ele escuta os seguintes sinais e tenta parar o host graciosamente. - Se o aplicativo chamar Environment.Exit.
A lógica de hospedagem interna lida com esses cenários, especificamente a ConsoleLifetime
classe. ConsoleLifetime
tenta lidar com os sinais de "desligamento" SIGINT, SIGQUIT e SIGTERM para permitir uma saída graciosa para o aplicativo.
Antes do .NET 6, não havia uma maneira de o código .NET lidar graciosamente com o SIGTERM. Para contornar esta limitação, ConsoleLifetime
subscreveria o System.AppDomain.ProcessExit. Quando ProcessExit
era levantado, ConsoleLifetime
sinalizava ao host para parar e bloquear o ProcessExit
thread, esperando que o host parasse.
O manipulador de saída do processo permitiria que o código de limpeza no aplicativo fosse executado — por exemplo, IHost.StopAsync e codificasse depois HostingAbstractionsHostExtensions.Run no Main
método.
No entanto, havia outros problemas com esta abordagem, porque o SIGTERM não era a única maneira ProcessExit
de ser levantado. SIGTERM também é gerado quando o código do aplicativo chama Environment.Exit
. Environment.Exit
não é uma maneira graciosa de encerrar um processo no Microsoft.Extensions.Hosting
modelo de aplicativo. Ele levanta o ProcessExit
evento e, em seguida, sai do processo. O final do Main
método não é executado. Os threads em segundo plano e em primeiro plano são encerrados e finally
os blocos não são executados.
Uma vez que ConsoleLifetime
bloqueado ProcessExit
enquanto aguardava o desligamento do host, esse comportamento levou a bloqueios de Environment.Exit
também blocos aguardando a chamada para ProcessExit
. Além disso, uma vez que a manipulação SIGTERM estava tentando encerrar graciosamente o processo, ConsoleLifetime
definiria o ExitCode para 0
, que clobbered o código de saída do usuário passado para Environment.Exit
.
No .NET 6, os sinais POSIX são suportados e manipulados. O ConsoleLifetime
manipula SIGTERM graciosamente, e não se envolve mais quando Environment.Exit
é invocado.
Gorjeta
Para o .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.ProcessExit
A hospedagem não tentará mais parar o host nesses cenários.
Se o seu aplicativo usa hospedagem e você deseja parar o host normalmente, você pode chamar IHostApplicationLifetime.StopApplication em vez de Environment.Exit
.
Processo de desligamento da 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 um entendimento profundo, um bom visual pode ajudá-lo a começar.
Depois que o host for iniciado, quando um usuário chamar Run
ou WaitForShutdown
, um manipulador será registrado para IApplicationLifetime.ApplicationStopping. A execução é pausada em WaitForShutdown
, aguardando que o ApplicationStopping
evento seja levantado. O Main
método 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
elevar oApplicationStopping
evento. Isso sinalizaWaitForShutdownAsync
para desbloquear o código deMain
execução. Enquanto isso, o manipulador de sinal POSIX retorna comCancel = true
desde que o sinal POSIX foi manipulado. - O
Main
código de execução começa a ser executado novamente e informa ao host paraStopAsync()
, que, por sua vez, interrompe todos os serviços hospedados e gera quaisquer outros eventos interrompidos. - Finalmente,
WaitForShutdown
sai, permitindo que qualquer código de limpeza de aplicativo seja executado e que o método saiaMain
normalmente.
Desligamento do host em cenários de servidor Web
Existem 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 diferentes ambientes com um balanceador de carga para drenar 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 sobre Configurar opções para a documentação do 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ê deve se inscrever neste evento se tiver alguma operação de longa duração que precise terminar normalmente.
Em seguida, o Host chama IServer.StopAsync com um tempo limite de desligamento que você pode configurar (padrão 30s). Kestrel (e Http.Sys) fecham suas ligações de porta e param de aceitar novas conexões. Eles também dizem às conexões atuais para parar de processar novas solicitações. Para HTTP/2 e HTTP/3, uma mensagem preliminar GOAWAY
é enviada ao cliente. Para HTTP/1.1, eles param o loop de conexão porque as solicitações são processadas em ordem. O IIS se comporta de forma 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 todos estiverem concluídos antes do tempo limite, o servidor devolverá o controle ao host mais cedo. Se o tempo limite expirar, as conexões e solicitações pendentes serão abortadas com força, o que pode causar erros nos logs e nos clientes.
Considerações sobre o 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 equilibrar o tráfego para ela (talvez você já tenha várias instâncias para fins de dimensionamento).
- Desative 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 encerrar.
- Espere até que ele drene ou tempo limite.