Host genérico de .NET
En este artículo, obtendrá información sobre los distintos patrones para configurar y compilar un host genérico de .NET disponible en el paquete NuGet Microsoft.Extensions.Hosting. El host genérico de .NET es responsable de la administración del inicio y la duración de la aplicación. Las plantillas de servicio de trabajo crean un host genérico de .NET, HostApplicationBuilder. 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.
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();
Para obtener más información sobre los servicios de trabajo, consulte Servicios de trabajo en .NET.
Configuración del generador de hosts
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
- Depurar
- 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
Al llamar a IHostBuilder.Build() o HostApplicationBuilder.Build(), los siguientes servicios se registran automáticamente:
Generadores de hosts basados en escenarios adicionales
Si va a compilar para la web o escribir una aplicación distribuida, es posible que tenga que usar otro generador de hosts. Tenga en cuenta la siguiente lista de generadores de hosts adicionales:
- DistributedApplicationBuilder: un generador para crear aplicaciones distribuidas. Para obtener más información, consulte .NET Aspire.
- WebApplicationBuilder: un generador para aplicaciones web y servicios. Para más información, consulte ASP.NET Core.
- WebHostBuilder: un generador para
IWebHost
. Para obtener más información, consulte Host web de ASP.NET Core.
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 y IHostedLifecycleService que registra los 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.");
}
}
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: 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.
La salida muestra el orden de todos los distintos eventos de ciclo de vida:
IHostedLifecycleService.StartingAsync
IHostedService.StartAsync
IHostedLifecycleService.StartedAsync
IHostApplicationLifetime.ApplicationStarted
Cuando se detiene la aplicación, por ejemplo, con Ctrl+C, se generan los siguientes eventos:
IHostApplicationLifetime.ApplicationStopping
IHostedLifecycleService.StoppingAsync
IHostedService.StopAsync
IHostedLifecycleService.StoppedAsync
IHostApplicationLifetime.ApplicationStopped
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.
La interfaz IHostLifetime
expone el método IHostLifetime.WaitForStartAsync, al que se llama al inicio de IHost.StartAsync
, que espera hasta que se complete antes de continuar. Esto se puede usar para retrasar el inicio hasta que lo indique un evento externo.
Además, la interfaz IHostLifetime
expone un método IHostLifetime.StopAsync, al que se llama desde IHost.StopAsync
para indicar que el host se está deteniendo y es el momento de apagarse.
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
Además, el servicio IHostEnvironment
expone la capacidad de evaluar el entorno con la ayuda de estos métodos de extensión:
- HostingEnvironmentExtensions.IsDevelopment
- HostingEnvironmentExtensions.IsEnvironment
- HostingEnvironmentExtensions.IsProduction
- HostingEnvironmentExtensions.IsStaging
Configuración de host
La configuración de host se usa para configurar las propiedades de la implementación de IHostEnvironment.
La configuración del host está disponible en la propiedad HostApplicationBuilderSettings.Configuration y la implementación del entorno está disponible en la propiedad IHostApplicationBuilder.Environment. Para configurar el host, acceda a la propiedad Configuration
y llame a cualquiera de los métodos de extensión disponibles.
Para agregar la configuración del host, considere el ejemplo siguiente:
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();
El código anterior:
- Establece la raíz de contenido en la ruta de acceso devuelta por GetCurrentDirectory().
- Carga la configuración de host de:
- hostsettings.json.
- Variables de entorno con el prefijo
PREFIX_
. - Argumentos de la línea de comandos.
Configuración de aplicaciones
La configuración de la aplicación se crea llamando a ConfigureAppConfiguration en IHostApplicationBuilder. La propiedad públicaIHostApplicationBuilder.Configuration permite a los consumidores leer la configuración existente o realizar cambios en ella mediante los métodos de extensión disponibles.
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/.