Negli script precedenti abbiamo introdotto alcuni concetti utili tutte le volte che vogliamo gestire dei task in background in ASP.NET Core: abbiamo visto i fondamenti per creare un Hosted Service (https://www.aspitalia.com/script/1396/Eseguire-Task-Temporizzati-Tramite-Hosted-Service-ASP.NET-Core.aspx), come iniettarvi correttamente servizi (https://www.aspitalia.com/script/1397/Iniettare-Servizi-Hosted-Service-ASP.NET-Core.aspx) e anche il modo più indicato per effettuarne lo shutdown (https://www.aspitalia.com/script/1398/Graceful-Shutdown-Hosted-Service-ASP.NET-Core.aspx).
Tutti questi esempi hanno in comune il medesimo limite: il servizio si avvia allo startup dell'applicazione, e termina solo al momento del suo shutdown. Spesso, invece, abbiamo bisogno di un controllo più preciso, per esempio potremmo voler temporaneamente arrestarne l'esecuzione.
Immaginiamo per esempio di avere timer che effettui il polling sul database, per controllare la modifica di un dato. In presenza di un'operazione bulk, che modifichi un migliaia di righe, magari può convenire interrompere il polling, che altrimenti finirebbe per segnalare un gran numero di notifiche consecutive, e riattivarlo solo al termine dell'operazione stessa.
Ciò che dobbiamo fare è innanzitutto recuperare una reference all'istanza dell'hosted service desiderato, per esempio tramite constructor injection:
private TimerService _timerService; public TimerController(IEnumerable<IHostedService> services) { _timerService = services.OfType<TimerService>().Single(); }
Per design, ASP.NET Core ammette un solo hosted service per tipo, quindi è corretto utilizzare la clausola Single una volta che abbiamo filtrato per il tipo di servizio desiderato.
A questo punto, possiamo invocarne i metodi StartAsync e StopAsync secondo le nostre necessità, per esempio come risposta a una chiamata HTTP POST:
[HttpPost] public async Task<IActionResult> SetStateAsync([FromBody] string newState) { switch (newState) { case "start": await _timerService.StartAsync(new CancellationToken()); break; case "stop": await _timerService.StopAsync(new CancellationToken()); break; default: return this.BadRequest(); } return this.Accepted(); }
Ovviamente, anche il codice del nostro hosted service dovrà essere scritto in modo da supportare correttamente Start e Stop multipli. Per esempio, la nostra versione originale non è adeguata, perché istanzia continuamente un nuovo oggetto Timer, con il risultato che chiamate multiple al metodo Start genererebbero diversi timer contemporaneamente in esecuzione:
public Task StartAsync(CancellationToken cancellationToken) { _timer = new Timer(...); return Task.CompletedTask; }
Una versione più corretta invece, deve verificare che il timer sia già istanziato, e in questo caso, limitarsi semplicemente a riattivarlo:
public class TimerService : IHostedService { private Timer _timer; private CancellationTokenSource _tokenSource; public Task StartAsync(CancellationToken cancellationToken) { _tokenSource = new CancellationTokenSource(); if (_timer == null) { _timer = new Timer(async s => { Console.WriteLine("timer trigger"); // istanziamo inizialmente come "spento" così da // evitare una prima doppia esecuzione }, null, Timeout.Infinite, Timeout.Infinite); } // qui attiviamo il timer _timer.Change(TimeSpan.Zero, TimeSpan.FromSeconds(10)); Console.WriteLine("timer started"); return Task.CompletedTask; } public Task StopAsync(CancellationToken cancellationToken) { _timer.Change(Timeout.Infinite, Timeout.Infinite); _tokenSource.Cancel(); Console.WriteLine("timer stopped"); return Task.CompletedTask; } }
Commenti
Per inserire un commento, devi avere un account.
Fai il login e torna a questa pagina, oppure registrati alla nostra community.
Approfondimenti
-
Registrare servizi multipli tramite chiavi in ASP.NET Core 8
-
Personalizzare l'errore del rate limiting middleware in ASP.NET Core
-
Eseguire query manipolando liste di tipi semplici con Entity Framework Core
-
Configurare dependabot per aggiornare le dipendenze di terze parti con GitHub Actions
-
Ottimizzare il mapping di liste di tipi semplici con Entity Framework Core
-
Load test di ASP.NET Core con k6
-
Evitare la command injection in un workflow di GitHub
-
Eseguire query verso tipi non mappati in Entity Framework Core
-
Eseguire attività con Azure Container Jobs
-
Usare il versioning con i controller di ASP.NET Core Web API
-
Potenziare Azure AI Search con la ricerca vettoriale
-
Cache policy su route groups di Minimal API in ASP.NET Core 7