Фоновые задачи: IHostedService, BackgroundService, Worker, Hangfire и Quartz

↑ 3.5 Инфраструктура приложения: ошибки, health checks, фоновые задачи · ← Предыдущая · → Следующая

Обязательно~3 мин чтенияУровень: middle

Зачем это на собесе

Что использовать для периодических и отложенных задач и как не потерять работу при перезапуске.

Объяснение

public class CleanupWorker(IServiceScopeFactory scopes, ILogger<CleanupWorker> log) : BackgroundService
{
    protected override async Task ExecuteAsync(CancellationToken ct)
    {
        using var timer = new PeriodicTimer(TimeSpan.FromMinutes(5));
        while (await timer.WaitForNextTickAsync(ct))
        {
            try
            {
                using var scope = scopes.CreateScope();
                await scope.ServiceProvider.GetRequiredService<ICleanup>().RunAsync(ct);
            }
            catch (Exception ex) when (ex is not OperationCanceledException)
            {
                log.LogError(ex, "Cleanup failed");   // цикл не должен умирать
            }
        }
    }
}
builder.Services.AddHostedService<CleanupWorker>();
ИнструментКогда
BackgroundServiceпростые периодические/длительные процессы, обработка очереди в памяти
Worker Serviceотдельный процесс без веб-части (консюмеры, обработчики)
Hangfireотложенные/повторяющиеся задачи, ретраи, дашборд, хранение в БД
Quartz.NETсложные расписания (cron), кластеризация
Outbox + очередьнадёжная доставка событий и задач

Нюансы и подводные камни

  • Исключение в ExecuteAsync (.NET 6+) останавливает приложение по умолчанию (BackgroundServiceExceptionBehavior.StopHost) — ловите внутри цикла.
  • Scoped-зависимости получайте через scope (см. Keyed services, фабрики, декораторы, IServiceScopeFactory).
  • В нескольких репликах задача выполнится в каждой: нужны distributed lock или планировщик со standalone-хранилищем.
  • Задачи в памяти теряются при перезапуске; критичное — в очередь/БД.
  • Уважайте CancellationToken для graceful shutdown.

Практика

  1. Напишите периодический воркер на PeriodicTimer с корректной обработкой ошибок.
  2. Подключите Hangfire с PostgreSQL и добавьте recurring job.
  3. Реализуйте distributed lock на Redis или advisory lock в PostgreSQL.

Вопросы с ответами

Связанные темы