Spesso nelle nostre applicazioni, ci troviamo a dover eseguire operazioni a intervalli regolari, per esempio per verificare se un utente è ancora connesso, pulire il contenuto di una tabella o effettuare il polling su una risorsa.
Una possibile soluzione, se siamo su Azure, è quella di utilizzare una Function con time trigger, con la complessità però di avere un'ulteriore "moving part" nella nostra architettura.
Un'alternativa più semplice è sfruttare gli hosted service di ASP.NET Core, che vengono eseguiti in automatico dal runtime come processi di background.
Ciò che dobbiamo fare è realizzare una classe che implementi l'interfaccia IHostedService:
internal class MyTimedService : IHostedService, IDisposable { private Timer _timer; private ILogger<MyTimedService> _logger; public MyTimedService(ILogger<MyTimedService> logger) { _logger = logger ?? throw new ArgumentNullException("logger"); } public Task StartAsync(CancellationToken cancellationToken) { _timer = new Timer( async state => { try { cancellationToken.ThrowIfCancellationRequested(); // ... nostra logica qui ... } catch (Exception ex) { _logger.LogError(ex, "Failed to execute"); } }, state:null, dueTime:TimeSpan.Zero, // questo è il delay per la prima esecuzione period:TimeSpan.FromMinutes(1)); // ogni minuto return Task.CompletedTask; } public Task StopAsync(CancellationToken cancellationToken) { _timer.Change(Timeout.Infinite, Timeout.Infinite); return Task.CompletedTask; } public void Dispose() { _timer.Dispose(); } }
Questa interfaccia si compone di due metodi, StartAsync e StopAsync, che il runtime invocherà per avviare e arrestare le operazioni.
In StartAsync creiamo un oggetto timer con un delegate che conterrà la nostra logica, da ripetere - nell'esempio - ogni minuto.
Il codice è piuttosto semplice, tuttavia ci sono un paio di accorgimenti a cui dobbiamo prestare attenzione. Il primo è relativo all'esecuzione, che deve avvenire all'interno di un blocco Try...Catch. In caso contrario, un'eccezione nel task porterebbe al crash dell'intera applicazione.
Il secondo è l'uso del cancellation token, che viene impostanto nel caso in cui è stato richiesto lo shutdown dell'applicazione durante l'avvio del nostro servizio. Nel nostro esempio verifichiamo lo stato all'inizio di ogni iterazione, ma, in casi più complessi, è buona norma passarlo anche alle componenti che lo supportano, in modo da interrompere task potenzialmente lunghi, come una richiesta HTTP per esempio.
Nella fase di StopAsync, invece, ci limitiamo a impostare il timer a Infinite per disabilitarlo.
Ultima nota riguarda l'interfaccia IDisposable. Questo metodo viene tipicamente richiamato in fase di shutdown, ma è sempre buona norma invocare il Dispose delle risorse utilizzate (in questo caso il nostro oggetto Timer).
Per attivare questo componente, è sufficiente registrarlo nei servizi durante la fase di startup:
public void ConfigureServices(IServiceCollection services) { services.AddControllers(); services.AddHostedService<MyTimedService>(); }
In un prossimo script, vedremo come iniettare servizi nel nostro task, così da renderlo ancora più versatile.
Commenti
Per inserire un commento, devi avere un account.
Fai il login e torna a questa pagina, oppure registrati alla nostra community.
Approfondimenti
-
Utilizzare l'attributo autofill del CSS
-
.NET 7 Live Q&A
-
Gestire il breaking change di Entity Framework Core 7 con tabelle che usano identity e trigger
-
Dependency Injection e custom validator in Blazor con .NET 7.0
-
Taggare la output cache in base al routing in ASP.NET Core
-
Gestire il fallimento di uno step in un workflow di GitHub
-
Sfruttare la local cache del browser tramite gli ETag in ASP.NET Core
-
Limitare lo spazio dei repository di Azure Container Registry con uno script bash e Azure CLI
-
Sfruttare l'output cache di ASP.NET Core 7 con i controller
-
Utilizzare la libreria EntityFrameworkCore.Exceptions per gestire le eccezioni di Entity Framework Core in modo tipizzato
-
DateOnly e TimeOnly in .NET: e io che ci faccio?
-
Raggruppare i parametri di una minimal API in un singolo oggetto in ASP.NET Core