Come usare BackgroundService in ASP.NET Core per task periodici

Come usare BackgroundService in ASP.NET Core per task periodici

Marco Puccio

🔍 Introduzione: perché servono i task periodici?

Nel ciclo di vita di un’applicazione server ci sono molti scenari in cui serve eseguire operazioni ricorrenti o in background, ad esempio:

✅ Pulizia di dati temporanei o vecchi
✅ Invio di email pianificate o promemoria
✅ Sincronizzazione con sistemi esterni (ERP, CRM, API)
✅ Monitoraggio di code o eventi
✅ Aggiornamento di cache o indici

Molti sviluppatori iniziano con Timer, Thread, Task.Run, ma in ambienti ASP.NET Core (soprattutto su cloud) queste soluzioni sono fragili e non gestiscono bene:

  • avvio insieme all’applicazione

  • riavvii o aggiornamenti controllati

  • graceful shutdown

  • dependency injection

Per risolvere questi problemi ASP.NET Core offre IHostedService e la sua implementazione astratta più comoda: BackgroundService.


🧭 Cosa sono IHostedService e BackgroundService?

ASP.NET Core è progettato come un generic host che può gestire più servizi.
IHostedService è un'interfaccia per definire componenti di lunga durata:

  • Viene avviato all’avvio dell’app

  • Può eseguire un ciclo infinito o task singoli

  • Gestisce in modo automatico il lifecycle (avvio e stop)

BackgroundService è una classe base fornita da Microsoft che implementa IHostedService semplificando il lavoro.
Tu devi solo overridare ExecuteAsync.


Caratteristiche chiave di BackgroundService

⭐️ Avvio automatico all'avvio dell'app
⭐️ Esecuzione asincrona e cancellabile
⭐️ Integrato con il DI container
⭐️ Graceful shutdown con CancellationToken
⭐️ Logging integrato


🧪 Scenario reale: pulizia di dati obsoleti

Immagina di avere una tabella TemporaryFiles che va pulita ogni ora per eliminare record più vecchi di 24 ore.

Vogliamo creare un servizio periodico che:

  • si avvia con l’app

  • gira finché il server è online

  • ogni ora pulisce i dati vecchi


🛠️ Passo 1 – Creare il servizio

Crea una classe che estende BackgroundService:

using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; public class CleanupService : BackgroundService { private readonly ILogger<CleanupService> _logger; private readonly ITemporaryFileRepository _repository; public CleanupService( ILogger<CleanupService> logger, ITemporaryFileRepository repository) { _logger = logger; _repository = repository; } protected override async Task ExecuteAsync(CancellationToken stoppingToken) { _logger.LogInformation("CleanupService avviato."); while (!stoppingToken.IsCancellationRequested) { try { _logger.LogInformation("Eseguo pulizia dei dati alle {time}", DateTimeOffset.Now); await _repository.DeleteOldFilesAsync(stoppingToken); } catch (Exception ex) { _logger.LogError(ex, "Errore durante la pulizia"); } // Aspetto un'ora prima del prossimo ciclo await Task.Delay(TimeSpan.FromHours(1), stoppingToken); } _logger.LogInformation("CleanupService in arresto."); } }

📌 Cosa succede qui?

✅ ExecuteAsync è il loop infinito controllato
✅ Rispettiamo il CancellationToken per shutdown ordinato
✅ Logging integrato per osservabilità
✅ Retry naturale a ogni intervallo


🛠️ Passo 2 – Registrazione nel DI container

Nel Program.cs di ASP.NET Core:

builder.Services.AddHostedService<CleanupService>(); builder.Services.AddScoped<ITemporaryFileRepository, TemporaryFileRepository>();

✅ AddHostedService dice al sistema di avviare il servizio all’avvio.
✅ Puoi usare tutti i servizi del tuo container!


🧩 Implementazione del repository (esempio semplificato)

public class TemporaryFileRepository : ITemporaryFileRepository { private readonly AppDbContext _db; public TemporaryFileRepository(AppDbContext db) { _db = db; } public async Task DeleteOldFilesAsync(CancellationToken cancellationToken) { var threshold = DateTime.UtcNow.AddHours(-24); var oldFiles = await _db.TemporaryFiles .Where(f => f.CreatedAt < threshold) .ToListAsync(cancellationToken); _db.TemporaryFiles.RemoveRange(oldFiles); await _db.SaveChangesAsync(cancellationToken); } }

✅ Supporto asincrono
✅ Usa Entity Framework e il CancellationToken
✅ Rispetta l’integrità transazionale


📈 Vantaggi di questa architettura

✅ Start & stop gestiti dal framework
✅ Integrato con la Dependency Injection
✅ Resiliente agli errori (try/catch nel loop)
✅ Scalabile su più ambienti (Docker, Azure App Service, K8s)
✅ Supporto nativo a graceful shutdown


Best Practice

✅ Rispetta sempre il CancellationToken
✅ Evita blocchi sincroni (Thread.Sleep)
✅ Usa logging dettagliato per il monitoring
✅ Evita lavori pesanti inline → usa queue o task separati
✅ Gestisci errori con retry (Polly, custom backoff)
✅ Monitora l’health check del servizio


🧭 Esempi di Use Case reali

  • Pulizia di sessioni scadute

  • Invio batch di email o notifiche push

  • Sincronizzazione con sistemi esterni

  • Aggiornamento di cache distribuite

  • Polling di code RabbitMQ/Azure Queue

  • Generazione periodica di report


🔥 Alternative per scenari più complessi

  • Hangfire per job persistenti e dashboard

  • Azure Functions Timer Trigger

  • AWS Lambda con EventBridge cron

  • Worker Service standalone in .NET


Conclusione

BackgroundService è lo standard in ASP.NET Core per operazioni periodiche e di lunga durata.
✔️ Semplice da implementare
✔️ Integrato con DI e logging
✔️ Gestione lifecycle professionale
✔️ Scalabile in cloud e container

Se devi schedulare task periodici in modo sicuro e mantenibile, questa è la strada giusta.