Implementare attività in background in microservizi con IHostedService e la classe BackgroundServiceImplement background tasks in microservices with IHostedService and the BackgroundService class

Le attività in background e i processi pianificati sono elementi che potrebbe essere necessario implementare, prima o poi, in un'applicazione basata su microservizi o in qualsiasi tipo di applicazione.Background tasks and scheduled jobs are something you might need to implement, eventually, in a microservice based application or in any kind of application. La differenza quando si usa un'architettura di microservizi consiste nella possibilità di implementare un singolo processo/contenitore di microservizi per ospitare queste attività in background così da poterlo ridurre o aumentare in base alle necessità, o persino assicurarsi che venga eseguita una sola istanza di tale processo/contenitore di microservizi.The difference when using a microservices architecture is that you can implement a single microservice process/container for hosting these background tasks so you can scale it down/up as you need or you can even make sure that it runs a single instance of that microservice process/container.

Da un punto di vista generico, in .NET Core questi tipi di attività vengono chiamati Servizi ospitati, perché sono servizi/logica ospitati all'interno di un host, un'applicazione o un microservizio.From a generic point of view, in .NET Core we called these type of tasks Hosted Services, because they are services/logic that you host within your host/application/microservice. Si noti che, in questo caso, il servizio ospitato indica semplicemente una classe con la logica di attività in background.Note that in this case, the hosted service simply means a class with the background task logic.

Fin dalla versione .NET Core 2.0, il framework fornisce una nuova interfaccia denominata IHostedService che consente di implementare facilmente i servizi ospitati.Since .NET Core 2.0, the framework provides a new interface named IHostedService helping you to easily implement hosted services. L'idea di base è che è possibile registrare più attività in background (servizi ospitati) che vengono eseguite in background mentre l'host o l'host Web è in esecuzione, come illustrato nell'immagine 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.

Diagramma che confronta ASP.NET Core IWebHost e .NET Core IHost.

Figura 6-26.Figure 6-26. Uso di IHostedService in un WebHost rispetto a un HostUsing IHostedService in a WebHost vs. a Host

ASP.NET Core 1. x e 2. x supportano IWebHost per i processi in background nelle app Web.ASP.NET Core 1.x and 2.x support IWebHost for background processes in web apps. .NET Core 2,1 supporta IHost per i processi in background con app console semplici..NET Core 2.1 supports IHost for background processes with plain console apps. Si noti la differenza tra WebHost e Host.Note the difference made between WebHost and Host.

Un WebHost (classe base che implementa IWebHost) in ASP.NET Core 2.0 è l'elemento infrastruttura che si usa per fornire funzionalità di server HTTP al processo, ad esempio durante l'implementazione di un'app Web MVC o un servizio 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 if you are implementing an MVC web app or Web API service. Offre tutta l'efficacia di una nuova infrastruttura in ASP.NET Core, consentendo di usare l'aggiunta di dipendenze, inserire middleware nella pipeline di richieste e così via e di usare con precisione le interfacce IHostedServices per le attività in background.It provides all the new infrastructure goodness in ASP.NET Core, enabling you to use dependency injection, insert middlewares in the request pipeline, etc. and precisely use these IHostedServices for background tasks.

In .NET Core 2.1 è stato introdotto un Host (classe di base che implementa IHost).A Host (base class implementing IHost) was introduced in .NET Core 2.1. In pratica, un Host consente di avere un'infrastruttura simile a quella che si ha con WebHost (aggiunta di dipendenze, i servizi ospitati e così via), ma in questo caso si avrà un processo semplice e più snello come host, senza elementi correlati a MVC, API Web o funzionalità del server HTTP.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.

Di conseguenza, è possibile scegliere e creare un processo host specializzato con IHost per gestire i servizi ospitati e nient'altro, ad esempio un microservizio realizzato solo per l'hosting di IHostedServices, oppure è possibile in alternativa estendere un WebHost esistente di ASP.NET Core, ad esempio un'API Web di ASP.NET Core o un'app MVC già esistente.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.

Ogni approccio presenta vantaggi e svantaggi in base alle esigenze aziendali e di scalabilità.Each approach has pros and cons depending on your business and scalability needs. In sostanza, se le attività in background non hanno nulla a che fare con HTTP (IWebHost), è consigliabile usare IHost.The bottom line is basically that if your background tasks have nothing to do with HTTP (IWebHost) you should use IHost.

Registrazione di servizi ospitati in un Host o WebHostRegistering hosted services in your WebHost or Host

Approfondiamo ulteriormente l'interfaccia di IHostedService perché il suo utilizzo è abbastanza simile in un WebHost o 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 è un esempio di un elemento che usa i servizi ospitati, ma è possibile usarlo anche per operazioni molto più semplici, quali:SignalR is one example of an artifact using hosted services, but you can also use it for much simpler things like:

  • Un'attività che esegue in background il polling di un database alla ricerca di modifiche.A background task polling a database looking for changes.
  • Un'attività pianificata di aggiornamento periodico della cache.A scheduled task updating some cache periodically.
  • Un'implementazione di QueueBackgroundWorkItem che consente di eseguire un'attività in un thread in background.An implementation of QueueBackgroundWorkItem that allows a task to be executed on a background thread.
  • L'elaborazione di messaggi da una coda di messaggi in background di un'app Web condividendo servizi comuni come ILogger.Processing messages from a message queue in the background of a web app while sharing common services such as ILogger.
  • Un'attività in background avviata con Task.Run().A background task started with Task.Run().

È praticamente possibile ripartire il carico di lavoro di qualsiasi azione in un'attività in background basata su IHostedService.You can basically offload any of those actions to a background task based on IHostedService.

Il modo in cui si aggiungono uno o più IHostedServices nel WebHost o Host consiste nel registrarli tramite il AddHostedServicemetodo di estensione  in un ASP.NET Core WebHost o in un Host in .NET Core 2,1 e versioni successive.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). In pratica, è necessario registrare i servizi ospitati all'interno del noto metodo ConfigureServices() della classe Startup, come illustrato nel codice seguente da un tipico WebHost ASP.NET.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>();
    //...
}

In questo codice, il servizio ospitato GracePeriodManagerService è il codice effettivo del microservizio aziendale Ordering in eShopOnContainers, mentre gli altri due sono solo esempi aggiuntivi.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.

L'esecuzione dell'attività in background IHostedService è coordinata con la durata dell'applicazione (a tal fine, host o microservizio).The IHostedService background task execution is coordinated with the lifetime of the application (host or microservice, for that matter). Si registrano le attività quando viene avviata l'applicazione e si ha la possibilità di eseguire un'azione automatica o di pulizia quando è in corso l'arresto dell'applicazione.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.

Senza usare IHostedService, è sempre possibile avviare un thread in background per eseguire qualsiasi attività.Without using IHostedService, you could always start a background thread to run any task. La differenza è precisamente nel tempo di arresto dell'app, quando il thread potrebbe essere semplicemente terminato senza la possibilità di eseguire operazioni di pulizia automatica.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.

Interfaccia IHostedServiceThe IHostedService interface

Quando si registra un IHostedService, .NET Core chiamerà i metodi StartAsync() e StopAsync() del tipo IHostedService rispettivamente durante l'avvio e l'arresto dell'applicazione.When you register an IHostedService, .NET Core will call the StartAsync() and StopAsync() methods of your IHostedService type during application start and stop respectively. In particolare, il metodo start viene chiamato dopo che il server è stato avviato e IApplicationLifetime.ApplicationStarted viene attivato.Specifically, start is called after the server has started and IApplicationLifetime.ApplicationStarted is triggered.

Il IHostedService definito in .NET Core sarà simile al seguente.The IHostedService as defined in .NET Core, looks like the following.

namespace Microsoft.Extensions.Hosting
{
    //
    // Summary:
    //     Defines methods for objects that are managed by the host.
    public interface IHostedService
    {
        //
        // Summary:
        // Triggered when the application host is ready to start the service.
        Task StartAsync(CancellationToken cancellationToken);
        //
        // Summary:
        // Triggered when the application host is performing a graceful shutdown.
        Task StopAsync(CancellationToken cancellationToken);
    }
}

Come è facile immaginare, è possibile creare più implementazioni di IHostedService e registrarle nel metodo ConfigureService() nel contenitore DI, come illustrato in precedenza.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. Tutti i servizi ospitati verranno avviati e arrestati con l'applicazione/microservizio.All those hosted services will be started and stopped along with the application/microservice.

Lo sviluppatore è responsabile della gestione dell'azione di arresto o dei servizi quando il metodo StopAsync() viene attivato dall'host.As a developer, you are responsible for handling the stopping action of your services when StopAsync() method is triggered by the host.

Implementazione di IHostedService con una classe personalizzata del servizio ospitato che deriva dalla classe base BackgroundServiceImplementing IHostedService with a custom hosted service class deriving from the BackgroundService base class

È possibile procedere alla creazione da zero di una classe personalizzata del servizio ospitato e implementare l'interfaccia IHostedService, come è necessario fare quando si usa .NET Core 2.0.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.

Tuttavia, poiché la maggior parte delle attività in background avrà esigenze simili per quanto riguarda la gestione dei token di annullamento e altre operazioni tipiche, è disponibile una classe di base astratta molto pratica per la derivazione, denominata BackgroundService a partire da .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).

Tale classe esegue il lavoro principale necessario per configurare l'attività in background.That class provides the main work needed to set up the background task.

Il codice successivo è la classe di base astratta BackgroundService nell'implementazione di .NET Core.The next code is the abstract BackgroundService base class as implemented in .NET Core.

// 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();
    }
}

Quando si deriva dalla classe base astratta precedente, grazie a tale implementazione ereditata, è sufficiente implementare il metodo ExecuteAsync() nella classe personalizzata del servizio ospitato, come illustrato di seguito nel codice semplificato da eShopOnContainers, che esegue il polling di un database e pubblica eventi di integrazione nel Bus di eventi quando necessario.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.");
    }

    .../...
}

In questo caso specifico per eShopOnContainers, viene eseguito un metodo dell'applicazione che cerca in una tabella di database gli ordini con uno stato specifico; quando vengono applicate le modifiche, pubblica eventi di integrazione usando il bus di eventi (al di sotto di questo potrebbe usare RabbitMQ o il bus di servizio di Azure).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).

Naturalmente, è possibile eseguire in alternativa qualsiasi altra attività di business in background.Of course, you could run any other business background task, instead.

Per impostazione predefinita, il token di annullamento è impostato con un timeout di 5 secondi, nonostante sia possibile modificare tale valore durante la compilazione di WebHost usando l'estensione UseShutdownTimeout di IWebHostBuilder.By default, the cancellation token is set with a 5 second timeout, although you can change that value when building your WebHost using the UseShutdownTimeout extension of the IWebHostBuilder. Ciò significa che il nostro servizio dovrebbe essere annullato entro 5 secondi; in caso contrario verrà terminato improvvisamente.This means that our service is expected to cancel within 5 seconds otherwise it will be more abruptly killed.

Il codice seguente permette di modificare tale intervallo di tempo in 10 secondi.The following code would be changing that time to 10 seconds.

WebHost.CreateDefaultBuilder(args)
    .UseShutdownTimeout(TimeSpan.FromSeconds(10))
    ...

Diagramma classi di riepilogoSummary class diagram

Nell'immagine seguente viene illustrato un riepilogo visivo delle classi e delle interfacce necessarie per l'implementazione di IHostedServices.The following image shows a visual summary of the classes and interfaces involved when implementing IHostedServices.

Diagramma che mostra che IWebHost e IHost possono ospitare molti servizi.

Figura 6-27.Figure 6-27. Diagramma classi che mostra più classi e interfacce correlate a IHostedServiceClass diagram showing the multiple classes and interfaces related to IHostedService

Diagramma classi: IWebHost e IHost possono ospitare molti servizi, che ereditano da BackgroundService, che implementa IHostedService.Class diagram: IWebHost and IHost can host many services, which inherit from BackgroundService, which implements IHostedService.

Considerazioni finali sulla distribuzioneDeployment considerations and takeaways

È importante notare che la modalità di distribuzione del WebHost di ASP.NET Core o del Host di .NET Core può incidere negativamente sulla soluzione finale.It is important to note that the way you deploy your ASP.NET Core WebHost or .NET Core Host might impact the final solution. Ad esempio, se si distribuisce il WebHost in IIS o in un normale Servizio App di Azure, l'host può essere arrestato a causa di ricicli del pool di app.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. Se invece si distribuisce l'host come contenitore in un agente di orchestrazione come Kubernetes o Service Fabric, è possibile controllare il numero garantito di istanze attive dell'host.But if you are deploying your host as a container into an orchestrator like Kubernetes or Service Fabric, you can control the assured number of live instances of your host. In più, è possibile considerare altri approcci nel cloud fatti apposta per questi scenari, ad esempio le Funzioni di Azure.In addition, you could consider other approaches in the cloud especially made for these scenarios, like Azure Functions. Infine, se è necessario che il servizio sia sempre in esecuzione e si esegue la distribuzione in un'istanza di Windows Server, è possibile usare un servizio di 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.

Tuttavia, anche per una WebHost distribuita in un pool di applicazioni, esistono scenari come il ripopolamento o lo scaricamento della cache in memoria dell'applicazione che sarebbe ancora applicabile.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.

L'interfaccia di IHostedService fornisce un modo pratico per avviare le attività in background in un'applicazione Web di ASP.NET Core (in .NET Core 2.0 ) o in qualsiasi processo/host (a partire da .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) or in any process/host (starting in .NET Core 2.1 with IHost). Il vantaggio principale è la possibilità che si ottiene con l'annullamento automatico di pulire il codice delle attività in background quando è in corso la chiusura dell'host stesso.Its main benefit is the opportunity you get with the graceful cancellation to clean-up code of your background tasks when the host itself is shutting down.

Risorse aggiuntiveAdditional resources