0Pricing
C# Academy · Урок

IHostedService и BackgroundService

Реализуйте IHostedService и абстрактный BackgroundService для выполнения фоновых задач внутри узла .NET.

«IHostedService и BackgroundService» — бесплатный урок C# Academy на CoddyKit. Это урок 1 из 4. Ты можешь прочитать весь урок бесплатно ниже — а потом практиковать его прямо в браузере с встроенным редактором кода и ИИ-репетитором 24/7. Это часть пути обучения C# Academy, и твой прогресс синхронизируется между веб-версией и приложением CoddyKit. Курс C# Academy содержит 4 уроков всего.

Что такое фоновые службы

Фоновые службы выполняют длительные задачи параллельно с приложением ASP.NET Core: обрабатывают очереди, запускают запланированные задачи или контролируют ресурсы. В .NET доступны два основных варианта: IHostedService (минимальный) и абстрактный BackgroundService (удобный для циклической работы).

Интерфейс IHostedService

IHostedService содержит два метода: StartAsync (вызывается при запуске хоста) и StopAsync (вызывается при корректном завершении работы). Он идеально подходит для однократных задач запуска и завершения.

public class DatabaseMigratorService : IHostedService
{
    private readonly IServiceScopeFactory _scopeFactory;

    public DatabaseMigratorService(IServiceScopeFactory sf) => _scopeFactory = sf;

    public async Task StartAsync(CancellationToken ct)
    {
        using var scope = _scopeFactory.CreateScope();
        var db = scope.ServiceProvider.GetRequiredService<AppDbContext>();
        await db.Database.MigrateAsync(ct);
    }

    public Task StopAsync(CancellationToken ct) => Task.CompletedTask;
}

builder.Services.AddHostedService<DatabaseMigratorService>();

BackgroundService: длительные циклы

BackgroundService — это абстрактный класс, реализующий IHostedService и предоставляющий ExecuteAsync(CancellationToken) — метод, который выполняется на протяжении всего времени жизни приложения.

public class HeartbeatService : BackgroundService
{
    private readonly ILogger<HeartbeatService> _logger;

    public HeartbeatService(ILogger<HeartbeatService> l) => _logger = l;

    protected override async Task ExecuteAsync(CancellationToken ct)
    {
        _logger.LogInformation("Heartbeat service started");

        while (!ct.IsCancellationRequested)
        {
            _logger.LogInformation("Heartbeat: {Time}", DateTime.UtcNow);
            await Task.Delay(TimeSpan.FromSeconds(30), ct);
        }

        _logger.LogInformation("Heartbeat service stopping");
    }
}

builder.Services.AddHostedService<HeartbeatService>();

Использование сервисов с ограниченным временем жизни

Фоновые службы являются одиночными экземплярами. Чтобы использовать сервисы с ограниченным временем жизни (например, DbContext), всегда создавайте новую область для каждой единицы работы с помощью IServiceScopeFactory.

public class DataSyncService : BackgroundService
{
    private readonly IServiceScopeFactory _factory;
    private readonly ILogger<DataSyncService> _log;

    public DataSyncService(IServiceScopeFactory f, ILogger<DataSyncService> l)
    { _factory = f; _log = l; }

    protected override async Task ExecuteAsync(CancellationToken ct)
    {
        while (!ct.IsCancellationRequested)
        {
            using (var scope = _factory.CreateScope())
            {
                var db = scope.ServiceProvider.GetRequiredService<AppDbContext>();
                await SyncDataAsync(db, ct);
            } // scope and DbContext disposed here

            await Task.Delay(TimeSpan.FromMinutes(5), ct);
        }
    }
}

Корректное завершение работы

При завершении работы хост активирует токен отмены. Ваш метод ExecuteAsync должен отслеживать этот токен и выполнять очистку. По умолчанию хост ожидает до 5 секунд, после чего принудительно останавливает процесс.

protected override async Task ExecuteAsync(CancellationToken ct)
{
    try
    {
        while (!ct.IsCancellationRequested)
        {
            await DoWorkAsync(ct);
            await Task.Delay(1000, ct);
        }
    }
    catch (OperationCanceledException)
    {
        // Normal shutdown — not an error
        _logger.LogInformation("Service stopping due to cancellation");
    }
    finally
    {
        // Cleanup
        await CleanupAsync();
    }
}

Фоновая очередь на основе каналов

Распространённый шаблон: фоновая служба читает элементы из очереди на основе каналов, а конечные точки HTTP добавляют в неё новые задачи. Это отделяет обработку запросов от выполнения задач.

public class BackgroundTaskQueue
{
    private readonly Channel<Func<CancellationToken, Task>> _queue
        = Channel.CreateBounded<Func<CancellationToken, Task>>(100);

    public ValueTask QueueAsync(Func<CancellationToken, Task> job)
        => _queue.Writer.WriteAsync(job);

    public IAsyncEnumerable<Func<CancellationToken, Task>> DequeueAllAsync(
        CancellationToken ct)
        => _queue.Reader.ReadAllAsync(ct);
}

// Register and use:
builder.Services.AddSingleton<BackgroundTaskQueue>();
builder.Services.AddHostedService<QueueProcessorService>();

Обработка ошибок в фоновых службах

Необработанные исключения в ExecuteAsync могут незаметно остановить службу. Оберните основной цикл в try/catch и журналируйте ошибки, чтобы служба продолжала работу несмотря на отдельные сбои.

protected override async Task ExecuteAsync(CancellationToken ct)
{
    while (!ct.IsCancellationRequested)
    {
        try
        {
            await ProcessNextBatchAsync(ct);
        }
        catch (OperationCanceledException) when (ct.IsCancellationRequested)
        {
            break; // normal shutdown
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "Background processing error. Retrying in 5s.");
            await Task.Delay(5000, ct); // backoff before retry
        }
    }
}

StopAsync и тайм-аут завершения

Переопределите StopAsync, чтобы выполнить очистку. Хост передаёт токен завершения — если очистка занимает больше времени, чем указано в тайм-ауте, процесс в любом случае будет завершён.

public override async Task StopAsync(CancellationToken stoppingToken)
{
    _logger.LogInformation("Service is stopping...");

    // Signal internal work to stop
    _internalCts.Cancel();

    // Wait for the Execute loop to complete (up to timeout)
    await base.StopAsync(stoppingToken);

    _logger.LogInformation("Service stopped");
}

// Increase the shutdown timeout if needed:
builder.Services.Configure<HostOptions>(opt =>
    opt.ShutdownTimeout = TimeSpan.FromSeconds(30));

Несколько фоновых служб

Зарегистрируйте несколько размещённых служб — все они будут выполняться одновременно. Хост запускает их по порядку, а останавливает в обратном порядке.

builder.Services.AddHostedService<DatabaseMigratorService>(); // startup task
builder.Services.AddHostedService<MetricsCollectorService>();  // continuous
builder.Services.AddHostedService<EmailNotificationService>(); // continuous
builder.Services.AddHostedService<CacheWarmupService>();       // startup task

// All run concurrently after app start
// Stopped in reverse registration order on shutdown

Практический пример: обработчик заказов

Фоновая служба, обрабатывающая ожидающие заказы из очереди базы данных с правильным созданием областей и обработкой ошибок.

public class OrderProcessorService : BackgroundService
{
    private readonly IServiceScopeFactory _factory;
    private readonly ILogger<OrderProcessorService> _log;

    public OrderProcessorService(IServiceScopeFactory f, ILogger<OrderProcessorService> l)
    { _factory = f; _log = l; }

    protected override async Task ExecuteAsync(CancellationToken ct)
    {
        while (!ct.IsCancellationRequested)
        {
            try
            {
                using var scope = _factory.CreateScope();
                var processor = scope.ServiceProvider.GetRequiredService<IOrderProcessor>();
                int count = await processor.ProcessPendingAsync(ct);
                _log.LogInformation("Processed {Count} orders", count);
            }
            catch (Exception ex) when (!ct.IsCancellationRequested)
            {
                _log.LogError(ex, "Order processing failed");
            }
            await Task.Delay(TimeSpan.FromSeconds(10), ct);
        }
    }
}

Быстрая проверка

Почему фоновые службы должны использовать IServiceScopeFactory, а не внедрять DbContext напрямую?

Итоги: IHostedService и BackgroundService

Главные выводы:

  • IHostedService: StartAsync/StopAsync — для задач запуска и завершения
  • BackgroundService: цикл ExecuteAsync — для непрерывной фоновой работы
  • Всегда используйте IServiceScopeFactory для зависимостей с ограниченным временем жизни внутри фоновых служб
  • Отслеживайте токен отмены при каждом await для корректного завершения работы
  • Оберните тело цикла в try/catch, чтобы служба не завершалась из-за ошибок
  • Используйте HostOptions.ShutdownTimeout, если очистка занимает больше 5 секунд

Часто задаваемые вопросы

Урок «IHostedService и BackgroundService» бесплатный?

Да — полный текст урока «IHostedService и BackgroundService» бесплатно доступен здесь в веб-версии. Чтобы практиковать его интерактивно (встроенный редактор кода и ИИ-репетитор 24/7) и разблокировать остальной курс C# Academy, подпишись на CoddyKit PRO. Курс C# Academy содержит 4 уроков всего.

Чему я научусь в уроке «IHostedService и BackgroundService»?

Реализуйте IHostedService и абстрактный BackgroundService для выполнения фоновых задач внутри узла .NET. Ты практикуешь C# Academy с помощью реального кода, который запускаешь прямо в браузере, и ИИ-репетитор 24/7 отвечает на твои вопросы во время урока.

Нужен ли мне опыт, чтобы начать C# Academy?

Предыдущий опыт не требуется. C# Academy на CoddyKit структурирован для всех уровней — от новичков до продвинутых, поэтому ты можешь начать отсюда или с самого начала и учиться в своем темпе. Это урок 1 из 4.

Сколько времени занимает урок «IHostedService и BackgroundService»?

Большинство уроков CoddyKit занимают около 5–10 минут. Каждый из них компактный и интерактивный, поэтому ты постоянно делаешь прогресс и продолжаешь с того же места в веб-версии и приложении.

Можно ли писать и запускать код в этом уроке C# Academy?

Да. Каждый урок C# Academy включает встроенный редактор кода, поэтому ты пишешь и запускаешь реальный код прямо в браузере и получаешь моментальную обратную связь от AI — локальная установка не требуется.

Все уроки этого курса

  1. IHostedService и BackgroundService
  2. Проекты рабочих служб
  3. Периодические задачи и таймеры
  4. Плановые задания Quartz.NET
← Назад к C# Academy