ASP.NET Core - Periodic Background Task

ASP.NET Core - Periodic Background Task

Možno ste mali vo vaších službách potrebu spustiť nejakú úlohu, ktorá sa bude vykonávať pravidelne na pozadí (aktualizácie keše, synchronizácia dát, mazanie starých dát, atď.).
V nových architekúrach možno siahnete po niečom ako AZURE Functions, AWS Lambda alebo iných serverless riešeniach.
Je ale dobré vedieť, že toto je možné spraviť aj priamo vo vašej ASP.NET Core službe. Stačí implementovať vlastnú hosted service pomocou rozhrania IHostedService.
Toto rozhranie definuje dve metódy StartAsync a StopAsync, ktoré sa zavolajú pri spustení a zastavení služby.

public interface IHostedService
{
    Task StartAsync(CancellationToken cancellationToken);

    Task StopAsync(CancellationToken cancellationToken);
}

Ale radšej ako vytvárať vlastnú implementáciu IHostedService, môžete zdediť abstraktnú triedu BackgroundService, ktorá vám uľahčí prácu.
Pri tejto triede môžete prepísať metódy StartAsync a StopAsync a v nich implementovať vašu logiku. Alebo stačí implementovať metódu ExecuteAsync a v nej implementovať len to čo chcete aby sa vykonávalo.

Nasledovný príklad ukazuje jednoduchú implementáciu BackgroundService, ktorá beží na pozadí a každých 5 sekúnd vypíše aktuálny čas.

// 👇 This is a simple implementation of a hosted service that runs a background task
public class PeriodicBackgroundTask(ILogger<PeriodicBackgroundTask> logger, TimeProvider timeProvider) 
    : BackgroundService
{
    private readonly ILogger<PeriodicBackgroundTask> _logger = logger;
    private readonly TimeProvider _timeProvider = timeProvider;

    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
        // 👇 In real-world scenarios, you should get the interval from configuration
        using var timer = new PeriodicTimer(TimeSpan.FromSeconds(5));

        // 👇 This loop will run every 5 seconds
        while (!stoppingToken.IsCancellationRequested
            && await timer.WaitForNextTickAsync(stoppingToken))
        {
            _logger.LogInformation("Current time: {CurrentTime}", _timeProvider.GetUtcNow().TimeOfDay);
        }
    }
}

Daný background task je potrebné zaragistrovať do DI kontainera ako hosted service pomocou metódy AddHostedService.

// 👇 Register the hosted service in the DI container
builder.Services.AddHostedService<PeriodicBackgroundTask>();

Do vášho background tasku môžete injectovať ľubovoľné služby, ktoré potrebujete pre jeho vykonávanie. Musia však byť registrované buď ako singleton alebo transient služby.
Preto ak potrebujete pristúpiť k scoped službám, musíte vytvoriť nový scope pomocou IServiceProvider.CreateScope() a závislosti získať z tohto scope.

// 👇 Use scoped dependency injection
public class ProductProcesSyncBackgroundTask(IServiceProvider serviceProvider) : BackgroundService
{
    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
        using var timer = new PeriodicTimer(TimeSpan.FromHours(2));

        while (!stoppingToken.IsCancellationRequested
            && await timer.WaitForNextTickAsync(stoppingToken))
        {
            // 👇 Create a new scope to resolve scoped services
            using var scope = serviceProvider.CreateScope();
            var service = scope.ServiceProvider.GetRequiredService<IProductPricesSyncService>();

            await service.SyncProductPricesAsync(stoppingToken);
        }
    }
}

ℹ️ V ďalšom článku ukážem ako si vytvoriť vlastné spracovávanie úloh na pozadí, ktoré budú zaradzované pomocou fronty.

Ak potrebujete zložitejšie plánovanie úloh, môžete použiť knižnice ako Hangfire alebo Quartz.NET, ktoré vám poskytnú viac možností a konfigurácií pre plánovanie úloh.