Implementar tareas en segundo plano en microservicios con IHostedService y la clase BackgroundServiceImplement background tasks in microservices with IHostedService and the BackgroundService class
Las tareas en segundo plano y los trabajos programados son elementos que podría necesitar para cualquier aplicación, tanto si esta sigue el patrón de arquitectura de microservicios como si no.Background tasks and scheduled jobs are something you might need to use in any application, whether or not it follows the microservices architecture pattern. La diferencia al usar una arquitectura de microservicios es que se puede implementar la tarea en segundo plano en un proceso o contenedor independiente para el hospedaje, de modo que se pueda modificar su escala en función de las necesidades.The difference when using a microservices architecture is that you can implement the background task in a separate process/container for hosting so you can scale it down/up based on your need.
Desde un punto de vista genérico, en .NET este tipo de tareas se llaman Servicios hospedados, puesto que son servicios o lógica que se hospedan en el host, la aplicación o el microservicio.From a generic point of view, in .NET we called these type of tasks Hosted Services, because they are services/logic that you host within your host/application/microservice. Observe que, en este caso, el servicio hospedado simplemente significa una clase con la lógica de la tarea de segundo plano.Note that in this case, the hosted service simply means a class with the background task logic.
Desde la versión 2.0 de .NET Core, el marco proporciona una nueva interfaz denominada IHostedService que le ayuda a implementar fácilmente servicios hospedados.Since .NET Core 2.0, the framework provides a new interface named IHostedService helping you to easily implement hosted services. La idea básica es que pueda registrar varias tareas en segundo plano (servicios hospedados), que se ejecutan en segundo plano mientras se ejecuta el host o host web, tal como se muestra en la imagen 6-26.The basic idea is that you can register multiple background tasks (hosted services) that run in the background while your web host or host is running, as shown in the image 6-26.
Figura 6-26.Figure 6-26. Uso de IHostedService en un WebHost frente a un HostUsing IHostedService in a WebHost vs. a Host
ASP.NET Core 1.x y 2.x admiten IWebHost
para los procesos en segundo plano en aplicaciones web.ASP.NET Core 1.x and 2.x support IWebHost
for background processes in web apps. .NET Core 2.1 y las versiones posteriores admiten IHost
para los procesos en segundo plano con aplicaciones de consola planas..NET Core 2.1 and later versions support IHost
for background processes with plain console apps. Observe la diferencia entre WebHost
y Host
.Note the difference made between WebHost
and Host
.
WebHost
(clase base que implementa IWebHost
) en ASP.NET Core 2.0 es el artefacto de infraestructura que se utiliza para proporcionar características de servidor HTTP al proceso, por ejemplo, si se va a implementar una aplicación web MVC o un servicio de API web.A WebHost
(base class implementing IWebHost
) in ASP.NET Core 2.0 is the infrastructure artifact you use to provide HTTP server features to your process, such as when you're implementing an MVC web app or Web API service. Proporciona todas las ventajas de la nueva infraestructura de ASP.NET Core, lo que le permite usar la inserción de dependencias e insertar middleware en la canalización de solicitudes, así como actividades similares.It provides all the new infrastructure goodness in ASP.NET Core, enabling you to use dependency injection, insert middlewares in the request pipeline, and similar. WebHost
usa estos IHostedServices
para las tareas en segundo plano.The WebHost
uses these very same IHostedServices
for background tasks.
Un Host
(clase base que implementa IHost
) se presentó en .NET Core 2.1.A Host
(base class implementing IHost
) was introduced in .NET Core 2.1. Básicamente, Host
permite disponer de una infraestructura similar a la que se tiene con WebHost
(inserción de dependencias, servicios hospedados, etc.), pero en este caso tan solo quiere tener un proceso sencillo y más ligero como host, sin ninguna relación con las características de servidor HTTP, MVC o API Web.Basically, a Host
allows you to have a similar infrastructure than what you have with WebHost
(dependency injection, hosted services, etc.), but in this case, you just want to have a simple and lighter process as the host, with nothing related to MVC, Web API or HTTP server features.
Por lo tanto, puede elegir y crear un proceso de host especializado con IHost
para controlar los servicios hospedados y nada más, como por ejemplo un microservicio hecho solo para hospedar IHostedServices
, o bien ampliar un elemento WebHost
de ASP.NET Core existente, como una aplicación MVC o API web de ASP.NET Core.Therefore, you can choose and either create a specialized host-process with IHost
to handle the hosted services and nothing else, such a microservice made just for hosting the IHostedServices
, or you can alternatively extend an existing ASP.NET Core WebHost
, such as an existing ASP.NET Core Web API or MVC app.
Cada enfoque tiene ventajas e inconvenientes dependiendo de sus necesidades empresariales y de escalabilidad.Each approach has pros and cons depending on your business and scalability needs. La conclusión es básicamente que, si las tareas en segundo plano no tienen nada que ver con HTTP (IWebHost
), debe usar IHost
.The bottom line is basically that if your background tasks have nothing to do with HTTP (IWebHost
) you should use IHost
.
Registro de servicios hospedados en Host o WebHostRegistering hosted services in your WebHost or Host
Vamos a profundizar más en la interfaz IHostedService
puesto que su uso es muy similar en un WebHost
o en un Host
.Let's drill down further on the IHostedService
interface since its usage is pretty similar in a WebHost
or in a Host
.
SignalR es un ejemplo de un artefacto con servicios hospedados, pero también puede utilizarlo para cosas mucho más sencillas como las siguientes:SignalR is one example of an artifact using hosted services, but you can also use it for much simpler things like:
- Una tarea en segundo plano que sondea una base de datos en busca de cambios.A background task polling a database looking for changes.
- Una tarea programada que actualiza una caché periódicamente.A scheduled task updating some cache periodically.
- Una implementación de QueueBackgroundWorkItem que permite que una tarea se ejecute en un subproceso en segundo plano.An implementation of QueueBackgroundWorkItem that allows a task to be executed on a background thread.
- Procesar los mensajes de una cola de mensajes en el segundo plano de una aplicación web mientras se comparten servicios comunes como
ILogger
.Processing messages from a message queue in the background of a web app while sharing common services such asILogger
. - Una tarea en segundo plano iniciada con
Task.Run()
.A background task started withTask.Run()
.
Básicamente, puede descargar cualquiera de esas acciones a una tarea en segundo plano que implementa IHostedService
.You can basically offload any of those actions to a background task that implements IHostedService
.
La forma de agregar uno o varios elementos IHostedServices
en WebHost
o Host
es registrarlos a través del método de extensión AddHostedService en un elemento WebHost
de ASP.NET Core (o en un elemento Host
en .NET Core 2.1 y versiones posteriores).The way you add one or multiple IHostedServices
into your WebHost
or Host
is by registering them up through the AddHostedService extension method in an ASP.NET Core WebHost
(or in a Host
in .NET Core 2.1 and above). Básicamente, tiene que registrar los servicios hospedados dentro del conocido método ConfigureServices()
de la clase Startup
, como se muestra en el código siguiente de un WebHost de ASP.NET típico.Basically, you have to register the hosted services within the familiar ConfigureServices()
method of the Startup
class, as in the following code from a typical ASP.NET WebHost.
public IServiceProvider ConfigureServices(IServiceCollection services)
{
//Other DI registrations;
// Register Hosted Services
services.AddHostedService<GracePeriodManagerService>();
services.AddHostedService<MyHostedServiceB>();
services.AddHostedService<MyHostedServiceC>();
//...
}
En el código, el servicio hospedado GracePeriodManagerService
es código real del microservicio de negocios de pedido en eShopOnContainers, mientras que los otros dos son solo dos ejemplos adicionales.In that code, the GracePeriodManagerService
hosted service is real code from the Ordering business microservice in eShopOnContainers, while the other two are just two additional samples.
La ejecución de la tarea en segundo plano IHostedService
se coordina con la duración de la aplicación (host o microservicio para este propósito).The IHostedService
background task execution is coordinated with the lifetime of the application (host or microservice, for that matter). Las tareas se registran cuando se inicia la aplicación y, cuando se esté cerrando la aplicación, tendrá la oportunidad de limpiar o realizar alguna acción correcta.You register tasks when the application starts and you have the opportunity to do some graceful action or clean-up when the application is shutting down.
Sin usar IHostedService
, siempre se puede iniciar un subproceso en segundo plano para ejecutar cualquier tarea.Without using IHostedService
, you could always start a background thread to run any task. La diferencia está precisamente en el momento de cierre de la aplicación, cuando ese subproceso simplemente terminaría sin tener ocasión de ejecutar las acciones de limpieza correcta.The difference is precisely at the app's shutdown time when that thread would simply be killed without having the opportunity to run graceful clean-up actions.
Interfaz de IHostedServiceThe IHostedService interface
Al registrar un servicio IHostedService
, .NET llamará a los métodos StartAsync()
y StopAsync()
de su tipo IHostedService
durante el inicio y la detención de la aplicación, respectivamente.When you register an IHostedService
, .NET will call the StartAsync()
and StopAsync()
methods of your IHostedService
type during application start and stop respectively. Para obtener más información, vea Interfaz IHostedService.For more details, refer IHostedService interface
Como puede imaginarse, es posible crear varias implementaciones de IHostedService y registrarlas en el método ConfigureService()
en el contenedor de DI, tal y como se ha mostrado anteriormente.As you can imagine, you can create multiple implementations of IHostedService and register them at the ConfigureService()
method into the DI container, as shown previously. Todos los servicios hospedados se iniciarán y detendrán junto con la aplicación o microservicio.All those hosted services will be started and stopped along with the application/microservice.
Los desarrolladores son responsables de controlar la acción de detención o los servicios cuando el host activa el método StopAsync()
.As a developer, you are responsible for handling the stopping action of your services when StopAsync()
method is triggered by the host.
Implementación de IHostedService con una clase de servicio hospedado personalizado que se deriva de la clase base BackgroundServiceImplementing IHostedService with a custom hosted service class deriving from the BackgroundService base class
Puede seguir adelante y crear una clase de servicio hospedado personalizado desde cero e implementar IHostedService
, tal y como se debe hacer cuando se usa .NET Core 2.0 y versiones posteriores.You could go ahead and create your custom hosted service class from scratch and implement the IHostedService
, as you need to do when using .NET Core 2.0 and later.
Pero como la mayoría de las tareas en segundo plano tienen necesidades similares en relación con la administración de tokens de cancelación y otras operaciones habituales, hay una clase base abstracta práctica denominada BackgroundService
de la que puede derivar (disponible desde .NET Core 2.1).However, since most background tasks will have similar needs in regard to the cancellation tokens management and other typical operations, there is a convenient abstract base class you can derive from, named BackgroundService
(available since .NET Core 2.1).
Esta clase proporciona el trabajo principal necesario para configurar la tarea en segundo plano.That class provides the main work needed to set up the background task.
El código siguiente es la clase base abstracta BackgroundService tal y como se implementa en .NET.The next code is the abstract BackgroundService base class as implemented in .NET.
// Copyright (c) .NET Foundation. Licensed under the Apache License, Version 2.0.
/// <summary>
/// Base class for implementing a long running <see cref="IHostedService"/>.
/// </summary>
public abstract class BackgroundService : IHostedService, IDisposable
{
private Task _executingTask;
private readonly CancellationTokenSource _stoppingCts =
new CancellationTokenSource();
protected abstract Task ExecuteAsync(CancellationToken stoppingToken);
public virtual Task StartAsync(CancellationToken cancellationToken)
{
// Store the task we're executing
_executingTask = ExecuteAsync(_stoppingCts.Token);
// If the task is completed then return it,
// this will bubble cancellation and failure to the caller
if (_executingTask.IsCompleted)
{
return _executingTask;
}
// Otherwise it's running
return Task.CompletedTask;
}
public virtual async Task StopAsync(CancellationToken cancellationToken)
{
// Stop called without start
if (_executingTask == null)
{
return;
}
try
{
// Signal cancellation to the executing method
_stoppingCts.Cancel();
}
finally
{
// Wait until the task completes or the stop token triggers
await Task.WhenAny(_executingTask, Task.Delay(Timeout.Infinite,
cancellationToken));
}
}
public virtual void Dispose()
{
_stoppingCts.Cancel();
}
}
Al derivar de la clase base abstracta anterior, y gracias a la implementación heredada, solo tiene que implementar el método ExecuteAsync()
en su clase de servicio hospedado personalizado propio, como en el siguiente ejemplo simplificado de código de eShopOnContainers, en el que se sondea una base de datos y se publican eventos de integración en el bus de eventos cuando es necesario.When deriving from the previous abstract base class, thanks to that inherited implementation, you just need to implement the ExecuteAsync()
method in your own custom hosted service class, as in the following simplified code from eShopOnContainers which is polling a database and publishing integration events into the Event Bus when needed.
public class GracePeriodManagerService : BackgroundService
{
private readonly ILogger<GracePeriodManagerService> _logger;
private readonly OrderingBackgroundSettings _settings;
private readonly IEventBus _eventBus;
public GracePeriodManagerService(IOptions<OrderingBackgroundSettings> settings,
IEventBus eventBus,
ILogger<GracePeriodManagerService> logger)
{
// Constructor's parameters validations...
}
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
_logger.LogDebug($"GracePeriodManagerService is starting.");
stoppingToken.Register(() =>
_logger.LogDebug($" GracePeriod background task is stopping."));
while (!stoppingToken.IsCancellationRequested)
{
_logger.LogDebug($"GracePeriod task doing background work.");
// This eShopOnContainers method is querying a database table
// and publishing events into the Event Bus (RabbitMQ / ServiceBus)
CheckConfirmedGracePeriodOrders();
await Task.Delay(_settings.CheckUpdateTime, stoppingToken);
}
_logger.LogDebug($"GracePeriod background task is stopping.");
}
.../...
}
En este caso concreto de eShopOnContainers, se ejecuta un método de aplicación que consulta una tabla de base de datos en la que busca pedidos con un estado específico y al aplicar los cambios, está publicando eventos de integración a través del bus de eventos (de forma subyacente puede estar utilizando RabbitMQ o Azure Service Bus).In this specific case for eShopOnContainers, it's executing an application method that's querying a database table looking for orders with a specific state and when applying changes, it is publishing integration events through the event bus (underneath it can be using RabbitMQ or Azure Service Bus).
Por supuesto, en su lugar puede ejecutar cualquier otra tarea en segundo plano empresarial.Of course, you could run any other business background task, instead.
De forma predeterminada, el token de cancelación se establece con un tiempo de espera de 5 segundos, aunque se puede cambiar ese valor al compilar su WebHost
mediante la extensión UseShutdownTimeout
de IWebHostBuilder
.By default, the cancellation token is set with a 5 seconds timeout, although you can change that value when building your WebHost
using the UseShutdownTimeout
extension of the IWebHostBuilder
. Esto significa que se espera que nuestro servicio se cancele en 5 segundos o, en caso contrario, se terminará de manera repentina.This means that our service is expected to cancel within 5 seconds otherwise it will be more abruptly killed.
El código siguiente cambiaría ese tiempo a 10 segundos.The following code would be changing that time to 10 seconds.
WebHost.CreateDefaultBuilder(args)
.UseShutdownTimeout(TimeSpan.FromSeconds(10))
...
Diagrama de clases de resumenSummary class diagram
En la siguiente ilustración se muestra un resumen visual de las clases y las interfaces implicadas al implementar IHostedServices.The following image shows a visual summary of the classes and interfaces involved when implementing IHostedServices.
Figura 6-27.Figure 6-27. Diagrama de clases que muestra las distintas clases e interfaces relacionadas con IHostedServiceClass diagram showing the multiple classes and interfaces related to IHostedService
Diagrama de clases: IWebHost y IHost pueden hospedar muchos servicios, que heredan de BackgroundService, que implementa IHostedService.Class diagram: IWebHost and IHost can host many services, which inherit from BackgroundService, which implements IHostedService.
Impresiones y consideraciones sobre implementaciónDeployment considerations and takeaways
Es importante tener en cuenta que la forma de implementar su WebHost
de ASP.NET Core o Host
de .NET puede afectar a la solución final.It is important to note that the way you deploy your ASP.NET Core WebHost
or .NET Host
might impact the final solution. Por ejemplo, si implementa su WebHost
en IIS o en un servicio de Azure App Service normal, el host se puede cerrar debido a reciclajes del grupo de aplicaciones.For instance, if you deploy your WebHost
on IIS or a regular Azure App Service, your host can be shut down because of app pool recycles. Pero si va a implementar el host como contenedor en un orquestador como Kubernetes, puede controlar el número garantizado de instancias activas del host.But if you are deploying your host as a container into an orchestrator like Kubernetes, you can control the assured number of live instances of your host. Además, podría considerar otros métodos en la nube pensados especialmente para estos escenarios, como Azure Functions.In addition, you could consider other approaches in the cloud especially made for these scenarios, like Azure Functions. Por último, si necesita que el servicio se ejecute todo el tiempo y se implemente en Windows Server, podría usar un servicio de Windows.Finally, if you need the service to be running all the time and are deploying on a Windows Server you could use a Windows Service.
Pero incluso para un elemento WebHost
implementado en un grupo de aplicaciones, hay escenarios, como el relleno o el vaciado de la memoria caché de la aplicación, en los que sería también aplicable.But even for a WebHost
deployed into an app pool, there are scenarios like repopulating or flushing application's in-memory cache that would be still applicable.
La interfaz IHostedService
proporciona una manera cómoda de iniciar tareas en segundo plano en una aplicación web de ASP.NET (en .NET Core 2.0 y versiones posteriores) o en cualquier proceso o host (a partir de .NET Core 2.1 con IHost
).The IHostedService
interface provides a convenient way to start background tasks in an ASP.NET Core web application (in .NET Core 2.0 and later versions) or in any process/host (starting in .NET Core 2.1 with IHost
). La principal ventaja es la oportunidad de obtener con la cancelación correcta un código de limpieza de sus tareas en segundo plano cuando se está cerrando el propio host.Its main benefit is the opportunity you get with the graceful cancellation to clean-up the code of your background tasks when the host itself is shutting down.
Recursos adicionalesAdditional resources
Creación de una tarea programada en ASP.NET Core/Standard 2.0 Building a scheduled task in ASP.NET Core/Standard 2.0
https://blog.maartenballiauw.be/post/2017/08/01/building-a-scheduled-cache-updater-in-aspnet-core-2.htmlImplementación de IHostedService en ASP.NET Core 2.0 Implementing IHostedService in ASP.NET Core 2.0
https://www.stevejgordon.co.uk/asp-net-core-2-ihostedserviceEjemplo de GenericHost mediante ASP.NET Core 2.1 GenericHost Sample using ASP.NET Core 2.1
https://github.com/aspnet/Hosting/tree/release/2.1/samples/GenericHostSample