Host genérico de .NET
Las plantillas de servicio de trabajo crean un host genérico de .NET, HostBuilder. El host genérico se puede usar con otros tipos de aplicaciones .NET, como aplicaciones de consola.
El host es un objeto que encapsula los recursos y la funcionalidad de vigencia de una aplicación, como:
- Inserción de dependencias (ID)
- Registro
- Configuración
- Apagado de la aplicación
- Implementaciones de
IHostedService
Cuando se inicia un host, llama a IHostedService.StartAsync en cada implementación de IHostedService registrada en la colección de servicios hospedados del contenedor de servicios. En una aplicación de servicio de trabajo, todas las implementaciones de IHostedService
que contienen instancias de BackgroundService tienen los métodos BackgroundService.ExecuteAsync a los que se llama.
La razón principal para incluir todos los recursos interdependientes de la aplicación en un objeto es la administración de la duración: el control sobre el inicio de la aplicación y el apagado estable. Haga referencia al paquete NuGet Microsoft.Extensions.Hosting para lograr la administración de la duración.
Configuración de un host
Normalmente se configura, compila y ejecuta el host por el código de la clase Program
. El método Main
realiza las acciones siguientes:
- Llama a un método CreateApplicationBuilder para crear y configurar un objeto del generador.
- Llama a Build() para crear una instancia de IHost.
- Llama al método Run o RunAsync en el objeto host.
Las plantillas de servicio de trabajo de .NET generan el código siguiente para crear un host genérico:
using Example.WorkerService;
HostApplicationBuilder builder = Host.CreateApplicationBuilder(args);
builder.Services.AddHostedService<Worker>();
IHost host = builder.Build();
host.Run();
Configuración predeterminada del generador
El método CreateApplicationBuilder realiza las acciones siguientes:
- Establece la raíz de contenido en la ruta de acceso devuelta por GetCurrentDirectory().
- Carga la configuración de host de:
- Variables de entorno con el prefijo
DOTNET_
. - Argumentos de la línea de comandos.
- Variables de entorno con el prefijo
- Carga la configuración de aplicación de:
- appsettings.json.
- appsettings.{Environment}.json.
- Administrador de secretos, cuando la aplicación se ejecuta en el entorno
Development
. - Variables de entorno.
- Argumentos de la línea de comandos.
- Agrega los siguientes proveedores de registro:
- Consola
- Depuración
- EventSource
- EventLog (solo si se ejecuta en Windows)
- Permite la validación del ámbito y la validación de dependencias si el entorno es
Development
.
HostApplicationBuilder.Services es una instancia Microsoft.Extensions.DependencyInjection.IServiceCollection. Estos servicios se usan para crear un IServiceProvider que se usa con la inserción de dependencias para resolver los servicios registrados.
Servicios proporcionados por el marco de trabajo
Los servicios siguientes se registran de forma automática:
IHostApplicationLifetime
Permite insertar el servicio IHostApplicationLifetime en cualquier clase para controlar las tareas posteriores al inicio y el cierre estable. Tres de las propiedades de la interfaz son tokens de cancelación que se usan para registrar los métodos del controlador de eventos de inicio y detención de las aplicaciones. La interfaz también incluye un método StopApplication().
El ejemplo siguiente es una implementación de IHostedService
que registra los eventos IHostApplicationLifetime
:
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
namespace AppLifetime.Example;
public sealed class ExampleHostedService : IHostedService
{
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);
}
public Task StartAsync(CancellationToken cancellationToken)
{
_logger.LogInformation("1. StartAsync has been called.");
return Task.CompletedTask;
}
public Task StopAsync(CancellationToken cancellationToken)
{
_logger.LogInformation("4. StopAsync has been called.");
return Task.CompletedTask;
}
private void OnStarted()
{
_logger.LogInformation("2. OnStarted has been called.");
}
private void OnStopping()
{
_logger.LogInformation("3. OnStopping has been called.");
}
private void OnStopped()
{
_logger.LogInformation("5. OnStopped has been called.");
}
}
La plantilla de servicio de trabajo se puede modificar para agregar la implementación de 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();
La aplicación escribiría la siguiente salida de ejemplo:
// Sample output:
// info: ExampleHostedService[0]
// 1. StartAsync has been called.
// info: ExampleHostedService[0]
// 2. 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\net7.0
// info: ExampleHostedService[0]
// 3. OnStopping has been called.
// info: Microsoft.Hosting.Lifetime[0]
// Application is shutting down...
// info: ExampleHostedService[0]
// 4. StopAsync has been called.
// info: ExampleHostedService[0]
// 5. OnStopped has been called.
IHostLifetime
La implementación de IHostLifetime controla cuándo se inicia el host y cuándo se detiene. Se usa la última implementación registrada. Microsoft.Extensions.Hosting.Internal.ConsoleLifetime
es la implementación predeterminada de IHostLifetime
. Para obtener más información sobre la mecánica de vigencia del apagado, consulte Apagado del host.
IHostEnvironment
Permite insertar el servicio IHostEnvironment en una clase para obtener información sobre los valores siguientes:
- IHostEnvironment.ApplicationName
- IHostEnvironment.ContentRootFileProvider
- IHostEnvironment.ContentRootPath
- IHostEnvironment.EnvironmentName
Configuración de host
La configuración de host se usa para configurar las propiedades de la implementación de IHostEnvironment.
La configuración de host está disponible en HostBuilderContext.Configuration dentro del método ConfigureAppConfiguration. Al llamar al método ConfigureAppConfiguration
, HostBuilderContext
y IConfigurationBuilder
se pasan a configureDelegate
. configureDelegate
se define como Action<HostBuilderContext, IConfigurationBuilder>
. El contexto del generador de hosts expone la propiedad Configuration
, que es una instancia de IConfiguration
. Representa la configuración generada a partir del host, mientras que IConfigurationBuilder
es el objeto de generador que se usa para configurar la aplicación.
Sugerencia
Después de llamar a ConfigureAppConfiguration
, HostBuilderContext.Configuration
se reemplaza por la configuración de la aplicación.
Para agregar la configuración de host, llame a ConfigureHostConfiguration en IHostBuilder
. Se puede llamar varias veces a ConfigureHostConfiguration
con resultados de suma. El host usa cualquier opción que establezca un valor en último lugar en una clave determinada.
En el ejemplo siguiente se crea la configuración de host:
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();
Configuración de aplicaciones
La configuración de la aplicación se crea llamando a ConfigureAppConfiguration en IHostBuilder
. Se puede llamar varias veces a ConfigureAppConfiguration
con resultados de suma. La aplicación usa cualquier opción que establezca un valor en último lugar en una clave determinada.
La configuración creada por ConfigureAppConfiguration
está disponible en HostBuilderContext.Configuration para las operaciones posteriores y como servicio de ID. La configuración de host también se agrega a la configuración de la aplicación.
Para obtener más información, vea Configuración en .NET.
Apagado del host
Hay varias maneras de detener un proceso hospedado. Por lo general, un proceso hospedado se puede detener de las maneras siguientes:
- Si alguien no llama a Run ni HostingAbstractionsHostExtensions.WaitForShutdown, y la aplicación se cierra normalmente luego de que
Main
finaliza. - Si la aplicación se bloquea.
- Si se fuerza el cierre de la aplicación mediante SIGKILL (o CTRL+Z).
El código de hospedaje no es responsable de controlar estos escenarios. El propietario del proceso debe tratar con ellos igual que cualquier aplicación. Hay varias maneras adicionales en las que se puede detener un proceso de servicio hospedado:
- Si se usa
ConsoleLifetime
(UseConsoleLifetime), escucha las siguientes señales e intenta detener el host correctamente. - Si la aplicación llama a Environment.Exit.
La lógica de hospedaje integrada controla estos escenarios, en particular la clase ConsoleLifetime
. ConsoleLifetime
intenta controlar las señales de "apagado" SIGINT, SIGQUIT y SIGTERM para permitir una salida correcta de la aplicación.
Antes de .NET 6, no había ninguna manera de que el código de .NET controlara correctamente SIGTERM. Para superar esta limitación, ConsoleLifetime
se suscribiría a System.AppDomain.ProcessExit. Al generar ProcessExit
, ConsoleLifetime
señalaría al host que detenga y bloquee el subproceso ProcessExit
, esperando a que el host se detenga.
El control de la salida del proceso permitiría que el código de limpieza de la aplicación se ejecutara; por ejemplo, IHost.StopAsync y código después de HostingAbstractionsHostExtensions.Run en el método Main
.
Sin embargo, hubo otros problemas con este enfoque porque SIGTERM no fue la única manera en que ProcessExit
se generó. SIGTERM también se genera cuando el código de la aplicación llama a Environment.Exit
. Environment.Exit
no es una manera correcta de cerrar un proceso en el modelo de aplicación Microsoft.Extensions.Hosting
. Genera el evento ProcessExit
y, a continuación, sale del proceso. El final del método Main
no se ejecuta. Los subprocesos en segundo plano y en primer plano finalizan, y los bloques finally
no se ejecutan.
Puesto que ConsoleLifetime
bloqueaba a ProcessExit
mientras esperaba a que el host se apagase, este comportamiento provocaba interbloqueos de Environment.Exit
y bloqueos a la espera de la llamada a ProcessExit
. Además, dado que el control SIGTERM estaba intentando cerrar el proceso correctamente, ConsoleLifetime
establecería ExitCode en 0
, lo que obstruyó el código de salida del usuario que se pasó a Environment.Exit
.
En .NET 6, se admiten y controlan las señales POSIX. ConsoleLifetime
controla SIGTERM correctamente y ya no se involucra cuando se invoca Environment.Exit
.
Sugerencia
Para .NET 6+, ConsoleLifetime
ya no tiene lógica para controlar el escenario Environment.Exit
. Las aplicaciones que llaman a Environment.Exit
y necesitan realizar una lógica de limpieza pueden suscribirse automáticamente a ProcessExit
. El hospedaje ya no intentará detener correctamente el host en estos escenarios.
Si la aplicación usa hospedaje, y usted quiere detener correctamente el host, puede llamar a IHostApplicationLifetime.StopApplication en lugar de a Environment.Exit
.
Proceso de apagado del hospedaje
En el siguiente diagrama de secuencia se muestra cómo se controlan internamente las señales en el código de hospedaje. La mayoría de los usuarios no necesita comprender este proceso. Pero para los desarrolladores que necesitan un conocimiento profundo, una buena visión puede resultar de ayuda para empezar.
Una vez iniciado el host, cuando un usuario llama a Run
o WaitForShutdown
, se registra un controlador para IApplicationLifetime.ApplicationStopping. La ejecución se pausa en WaitForShutdown
, a la espera de que se pueda generar el evento ApplicationStopping
. El método Main
no se devuelve de inmediato, y la aplicación permanece en ejecución hasta que se devuelve Run
o WaitForShutdown
.
Cuando se envía una señal al proceso, inicia la secuencia siguiente:
- El control fluye de
ConsoleLifetime
aApplicationLifetime
para generar el eventoApplicationStopping
. Esto indica queWaitForShutdownAsync
desbloquee el código de ejecución deMain
. Mientras tanto, el controlador de señal POSIX se devuelve conCancel = true
, ya que se ha controlado esta señal POSIX. - El código de ejecución de
Main
comienza a ejecutarse de nuevo e indica al host queStopAsync()
, lo que, a su vez, detiene todos los servicios hospedados y genera cualquier otro evento detenido. - Por último,
WaitForShutdown
se cierra, lo que permite que cualquier aplicación limpie el código que se va a ejecutar y que el métodoMain
salga correctamente.
Apagado del host en escenarios de servidor web
Hay otros escenarios comunes en los que el apagado correcto funciona en Kestrel para los protocolos HTTP/1.1 y HTTP/2, y cómo puede configurarlo en diferentes entornos con un equilibrador de carga para purgar el tráfico sin problemas. Aunque la configuración del servidor web está fuera del ámbito de este artículo, puede encontrar más información en Configuración de opciones para el servidor web Kestrel de ASP.NET Core.
Cuando el host recibe una señal de apagado (por ejemplo, CTL+C o StopAsync
), lo notifica a la aplicación con la señal ApplicationStopping. Debe suscribirse a este evento si tiene operaciones de larga duración que necesiten finalizarse correctamente.
A continuación, el host llama a IServer.StopAsync con un tiempo de espera de apagado que puede configurar (el valor predeterminado es 30 s). Kestrel (y Http.Sys) cierran sus enlaces de puerto y dejan de aceptar nuevas conexiones. También indican a las conexiones actuales que detengan el procesamiento de nuevas solicitudes. Para HTTP/2 y HTTP/3, se envía un mensaje preliminar GOAWAY
al cliente. Para HTTP/1.1, detienen el bucle de conexión porque las solicitudes se procesan en orden. IIS se comporta de forma diferente al rechazar nuevas solicitudes con un código de estado 503.
Las solicitudes activas tienen para completarse hasta que se agota el tiempo de espera de apagado. Si se completan antes del tiempo de espera, el servidor devuelve el control al host antes. Si expira el tiempo de espera, las conexiones y solicitudes pendientes se anulan de manera forzosa, lo que puede provocar errores en los registros y en los clientes.
Consideraciones sobre el equilibrador de carga
Para garantizar una transición fluida de los clientes a un nuevo destino al trabajar con un equilibrador de carga, puede seguir estos pasos:
- Abra la nueva instancia y empiece a equilibrar el tráfico (es posible que ya tenga varias instancias con fines de escalado).
- Deshabilite o quite la instancia anterior en la configuración del equilibrador de carga para que deje de recibir tráfico nuevo.
- Señale la instancia anterior para apagarla.
- Espere a que se purgue o a que se agote el tiempo de espera.
Vea también
- Inserción de dependencias en .NET
- Registro en .NET
- Configuración en .NET
- Servicios Worker en .NET
- Host web de ASP.NET Core
- Configuración del servidor web Kestrel de ASP.NET Core
- Los errores de host genérico deben crearse en el repositorio github.com/dotnet/runtime/.
Comentarios
Enviar y ver comentarios de