Avviare e arrestare programmaticamente un Hosted Service di ASP.NET Core

di Marco De Sanctis, in ASP.NET Core,

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

Visualizza/aggiungi commenti

| Condividi su: Twitter, Facebook, LinkedIn

Per inserire un commento, devi avere un account.

Fai il login e torna a questa pagina, oppure registrati alla nostra community.

Approfondimenti

I più letti di oggi