Перейти к содержанию

Использование сервисов с жизненным циклом Scope в фоновых задачах

Фоновые сервисы, реализующие IHostedService, создаются один раз при запуске приложения, то есть, фактически являются синглтонами. Как быть, если нужно использовать сервисы с жизненным циклом Scoped?
Разберем следующий пример. Немного изменим пример с кешированием из предыдущего раздела, и станем хранить курсы в БД.
DbContext от EF Core регистрируется с жизненным циклом Scoped, что означает необходимость получать его из ExchangeRatesHostedService, но не через внедрение в конструкторе.
В типичном ASP.NET Core приложении фреймворк создаёт новую область (Scope) контейнера при получении запроса, перед выполнением конвейера промежуточного ПО. Однако в фоновом сервисе нет запросов, поэтому новые области не создаются. Поэтому надо создавать новую область самим. Для этого используется метод IServiceProvider.CreateScope().
Получаемая таким образом область IServiceScope реализует интерфейс IDisposable.

Внимание!

Не забывайте избавляться (Dispose) от области IServiceScope после окончания использования — так вы предотвратите утечку памяти. Лучше всего использовать инструкцию using.

В следующем примере перепишем ExchangeRatesHostedService, создавая новую область для каждой итерации цикла while.

public class ExchangeRatesHostedService: BackgroundService
{
    private readonly IServiceProvider _provider;
    public ExchangeRatesHostedService(IServiceProvider provider)
    {
        _provider = provider;
    }

    protected override async Task ExecuteAsync(CancellationToken token)
    {
        while (!token.IsCancellationRequested)
        {
            using(IServiceScope scope = _provider.CreateScope())
            {
                var scopedProvider = scope.ServiceProvider;
                var client = scopedProvider
                    .GetRequiredService<ExchangeRatesClient>();
                var context = scopedProvider
                    .GetRequiredService<AppDbContext>();

                var rates = await client.GetLatestRatesAsync();

                context.Add(rates);
                await context.SaveChanges(rates);
            }
            await Task.Delay(TimeSpan.FromMinutes(5), token);
        }
    }
}

Создание таких областей — общее решение, когда нужен доступ к сервисам с жизненным циклом Scoped вне контекста запроса. Например реализация интерфейса IConfigureOptions.

Совет

Чтобы сделать использование локатора сервисов менее запутанным, рекомендуется выделять тело задачу в отдельный класс и использовать локацию сервисов только для этого класса, как показано в разделе “Использование службы с заданной областью в фоновой задаче” этого документа


Последнее обновление : 12 мая 2023 г.
Дата создания : 12 ноября 2022 г.

Комментарии

Комментарии