Как лучше инжектировать зависимости для фоновой задачи, которая циклично выполняется и засыпает на 24 часа?
Что проверяют
Знаете ли вы, что фоновый сервис живёт синглтоном всё время работы приложения, и что будет, если внедрить в него scoped-зависимость вроде DbContext. Следом спрашивают, как пережить рестарт и что делать, если задача должна идти строго по расписанию.
Как отвечать
Каркас на современном .NET:
BackgroundService: переопределяетеExecuteAsync(CancellationToken stoppingToken), регистрируете черезbuilder.Services.AddHostedService<NightlyJob>()вProgram.cs;- в конструктор только синглтоны:
ILogger<T>,IOptionsMonitor<T>,TimeProviderи главноеIServiceScopeFactory; - scope на каждую итерацию:
await using var scope = scopeFactory.CreateAsyncScope();, затемGetRequiredService<IReportJob>()изscope.ServiceProvider. ТакDbContextсоздаётся свежим и освобождается после прогона, а не живёт сутками; - ожидание:
PeriodicTimerсWaitForNextTickAsync(stoppingToken)илиTask.Delay(TimeSpan.FromHours(24), stoppingToken). Оба уважают токен остановки, в отличие отSystem.Threading.Timer, чей колбэк синхронный, а прогоны могут наложиться; - исключения: тело итерации в
try/catchс логированием. С .NET 6 необработанное исключение вExecuteAsyncпо умолчанию останавливает весь хост.
Логику вынесите в отдельный класс за интерфейсом, а сервис оставьте тонкой обёрткой над циклом: её легко протестировать, а время подменить через FakeTimeProvider.
И назовите ограничение: «уснуть на 24 часа» сбивается при каждом деплое и дублируется на нескольких репликах. Для «каждый день в 03:00» честнее Quartz.NET или Hangfire с cron-выражением и хранилищем.
Где спотыкаются
Внедряют DbContext прямо в конструктор. В Development приложение падает при старте с ошибкой про scoped-сервис в синглтоне, а в Production молча получается один контекст на всю жизнь процесса.
Пишут async void в колбэке таймера. Исключение оттуда роняет процесс, а не попадает в логи.
Не передают stoppingToken в Task.Delay и запросы. Остановка тогда ждёт таймаута хоста и обрывает задачу на середине.