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:

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.
  • 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:

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:

  1. IHostedLifecycleService.StartingAsync
  2. IHostedService.StartAsync
  3. IHostedLifecycleService.StartedAsync
  4. IHostApplicationLifetime.ApplicationStarted

Quando o aplicativo é interrompido, por exemplo, com Ctrl+C, os seguintes eventos são gerados:

  1. IHostApplicationLifetime.ApplicationStopping
  2. IHostedLifecycleService.StoppingAsync
  3. IHostedService.StopAsync
  4. IHostedLifecycleService.StoppedAsync
  5. 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:

Além disso, o IHostEnvironment serviço expõe a capacidade de avaliar o ambiente com a ajuda destes métodos de extensão:

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 IHostApplicationBuilder.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;

HostApplicationBuilder builder = Host.CreateApplicationBuilder(args);

builder.Environment.ContentRootPath = Directory.GetCurrentDirectory();
builder.Configuration.AddJsonFile("hostsettings.json", optional: true);
builder.Configuration.AddEnvironmentVariables(prefix: "PREFIX_");
builder.Configuration.AddCommandLine(args);

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:

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.
    • SIGINT (ou CTRL+C).
    • SIGQUIT (ou CTRL+BREAK no Windows, CTRL+\ no Unix).
    • SIGTERM (enviado por outros aplicativos, como docker stop).
  • 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:

Diagrama de sequência de desligamento de hospedagem.

  1. O controle flui de ConsoleLifetime para ApplicationLifetime elevar o ApplicationStopping evento. Isso sinaliza WaitForShutdownAsync para desbloquear o código de Main execução. Enquanto isso, o manipulador de sinal POSIX retorna com Cancel = true desde que o sinal POSIX foi manipulado.
  2. O Main código de execução começa a ser executado novamente e informa ao host para StopAsync(), que, por sua vez, interrompe todos os serviços hospedados e gera quaisquer outros eventos interrompidos.
  3. Finalmente, WaitForShutdown sai, permitindo que qualquer código de limpeza de aplicativo seja executado e que o método saia Main 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.

Consulte também