Implementare attività in background in microservizi con IHostedService e la classe BackgroundService
Suggerimento
Questo contenuto è un estratto da eBook, architettura di microservizi .NET per applicazioni .NET in contenitori, disponibili in .NET Docs o come PDF scaricabile gratuitamente che può essere letto offline.
Le attività in background e i processi pianificati sono un elemento che potrebbe essere necessario usare in qualsiasi applicazione, indipendentemente dal fatto che sia conforme al modello di architettura dei microservizi. La differenza quando si usa un'architettura di microservizi è che è possibile implementare l'attività in background in un processo/contenitore separato per l'hosting in modo da poterlo ridimensionare/aumentare in base alle esigenze.
Da un punto di vista generico, in .NET è stato chiamato questo tipo di attività Hosted Services, perché sono servizi/logica che si ospitano all'interno dell'host/applicazione/microservizio. Si noti che, in questo caso, il servizio ospitato indica semplicemente una classe con la logica di attività in background.
Fin dalla versione .NET Core 2.0, il framework fornisce una nuova interfaccia denominata IHostedService che consente di implementare facilmente i servizi ospitati. L'idea di base è che è possibile registrare più attività in background (servizi ospitati) eseguite in background mentre l'host Web o l'host è in esecuzione, come illustrato nell'immagine 6-26.
Figura 6-26. Uso di IHostedService in un WebHost rispetto a un Host
ASP.NET Core supporto IWebHost
1.x e 2.x per i processi in background nelle app Web. .NET Core 2.1 e versioni successive supportano IHost
i processi in background con app console semplici. Si noti la differenza tra WebHost
e Host
.
Una WebHost
(classe base che implementa IWebHost
) in ASP.NET Core 2.0 è l'artefatto dell'infrastruttura usato per fornire funzionalità del server HTTP al processo, ad esempio quando si implementa un'app Web MVC o un servizio API Web MVC. Fornisce tutta la nuova bontà dell'infrastruttura in ASP.NET Core, consentendo di usare l'inserimento delle dipendenze, inserire middleware nella pipeline della richiesta e simili. Usa WebHost
queste operazioni molto stesse IHostedServices
per le attività in background.
In .NET Core 2.1 è stato introdotto un Host
(classe di base che implementa IHost
). 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.
Pertanto, è possibile scegliere e creare un processo host specializzato con IHost
per gestire i servizi ospitati e niente altro, tale microservizio fatto solo per ospitare , IHostedServices
oppure è possibile estendere in alternativa un ASP.NET Core WebHost
esistente, ad esempio un'API Web esistente o ASP.NET Core un'app MVC.
Ogni approccio presenta vantaggi e svantaggi in base alle esigenze aziendali e di scalabilità. La riga inferiore è fondamentalmente che se le attività in background non hanno nulla a che fare con HTTP (IWebHost
) è consigliabile usare IHost
.
Registrazione di servizi ospitati in un Host o WebHost
Eseguire il drill-down più avanti sull'interfaccia perché l'utilizzo IHostedService
è piuttosto simile in un WebHost
o in un Host
.
SignalR è un esempio di un elemento che usa i servizi ospitati, ma è possibile usarlo anche per operazioni molto più semplici, quali:
- Un'attività che esegue in background il polling di un database alla ricerca di modifiche.
- Un'attività pianificata di aggiornamento periodico della cache.
- Un'implementazione di QueueBackgroundWorkItem che consente di eseguire un'attività in un thread in background.
- L'elaborazione di messaggi da una coda di messaggi in background di un'app Web condividendo servizi comuni come
ILogger
. - Un'attività in background avviata con
Task.Run()
.
È possibile disattivare fondamentalmente una di queste azioni a un'attività in background che implementa IHostedService
.
Il modo in cui si aggiunge uno o più IHostedServices
WebHost
nell'utente o Host
lo registra tramite il AddHostedService metodo di estensione in un ASP.NET Core WebHost
(o in Host
.NET Core 2.1 e versioni successive). In pratica, è necessario registrare i servizi ospitati all'interno dell'avvio dell'applicazione in Program.cs.
//Other DI registrations;
// Register Hosted Services
builder.Services.AddHostedService<GracePeriodManagerService>();
builder.Services.AddHostedService<MyHostedServiceB>();
builder.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.
L'esecuzione dell'attività in background IHostedService
è coordinata con la durata dell'applicazione (a tal fine, host o microservizio). 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.
Senza usare IHostedService
, è sempre possibile avviare un thread in background per eseguire qualsiasi attività. La differenza è esattamente al momento dell'arresto dell'app quando il thread sarebbe stato semplicemente ucciso senza avere l'opportunità di eseguire azioni di pulizia grazia.
Interfaccia IHostedService
Quando si registra un IHostedService
oggetto , .NET chiamerà rispettivamente i metodi e StopAsync()
del tipo durante l'avvio StartAsync()
e l'arresto dell'applicazioneIHostedService
. Per altre informazioni, vedere Interfaccia IHostedService
Come si può immaginare, è possibile creare più implementazioni di IHostedService e registrarle in Program.cs, come illustrato in precedenza. Tutti i servizi ospitati verranno avviati e arrestati con l'applicazione/microservizio.
Lo sviluppatore è responsabile della gestione dell'azione di arresto o dei servizi quando il metodo StopAsync()
viene attivato dall'host.
Implementazione di IHostedService con una classe personalizzata del servizio ospitato che deriva dalla classe base BackgroundService
È possibile procedere e creare la classe di servizio ospitata personalizzata da zero e implementare , IHostedService
come è necessario eseguire quando si usa .NET Core 2.0 e versioni successive.
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.
Tale classe esegue il lavoro principale necessario per configurare l'attività in background.
Il codice successivo è la classe base BackgroundService astratta come implementata 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();
}
}
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.
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();
try {
await Task.Delay(_settings.CheckUpdateTime, stoppingToken);
}
catch (TaskCanceledException exception) {
_logger.LogCritical(exception, "TaskCanceledException Error", exception.Message);
}
}
_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).
Naturalmente, è possibile eseguire in alternativa qualsiasi altra attività di business in background.
Per impostazione predefinita, il token di annullamento viene impostato con un timeout di 5 secondi, anche se è possibile modificare tale valore durante la compilazione WebHost
dell'estensione dell'oggetto UseShutdownTimeout
IWebHostBuilder
. Ciò significa che il nostro servizio dovrebbe essere annullato entro 5 secondi; in caso contrario verrà terminato improvvisamente.
Il codice seguente permette di modificare tale intervallo di tempo in 10 secondi.
WebHost.CreateDefaultBuilder(args)
.UseShutdownTimeout(TimeSpan.FromSeconds(10))
...
Diagramma classi di riepilogo
L'immagine seguente mostra un riepilogo visivo delle classi e delle interfacce coinvolte durante l'implementazione di IHostedServices.
Figura 6-27. Diagramma classi che mostra più classi e interfacce correlate a IHostedService
Diagramma classi: IWebHost e IHost possono ospitare molti servizi, che ereditano da BackgroundService, che implementa IHostedService.
Considerazioni finali sulla distribuzione
È importante notare che il modo in cui si distribuisce il ASP.NET Core WebHost
o .NET Host
potrebbe influire sulla soluzione finale. 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. Tuttavia, se si distribuisce l'host come contenitore in un agente di orchestrazione come Kubernetes, è possibile controllare il numero garantito di istanze live dell'host. In più, è possibile considerare altri approcci nel cloud fatti apposta per questi scenari, ad esempio le Funzioni di Azure. 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.
Ma anche per un WebHost
pool di app distribuito in un pool di app, esistono scenari come la ripopolazione o lo scaricamento della cache in memoria dell'applicazione che sarebbe ancora applicabile.
L'interfaccia IHostedService
offre un modo pratico per avviare attività in background in un'applicazione Web ASP.NET Core (in .NET Core 2.0 e versioni successive) o in qualsiasi processo/host (a partire da .NET Core 2.1 con IHost
). Il suo vantaggio principale è l'opportunità di ottenere l'annullamento grazia per pulire il codice delle attività in background quando l'host stesso sta arrestando.
Risorse aggiuntive
Creare un'attività pianificata in ASP.NET Core/Standard 2.0
https://blog.maartenballiauw.be/post/2017/08/01/building-a-scheduled-cache-updater-in-aspnet-core-2.htmlImplementazione di IHostedService in ASP.NET Core 2.0
https://www.stevejgordon.co.uk/asp-net-core-2-ihostedserviceGenericHost Sample using ASP.NET Core 2.1 (Esempio di GenericHost con ASP.NET Core 2.1)
https://github.com/aspnet/Hosting/tree/release/2.1/samples/GenericHostSample