Ответ
Предпочтительный способ — использование IHostedService и его удобной абстракции BackgroundService. Они интегрированы в систему зависимостей и жизненный цикл хоста приложения, что обеспечивает корректный запуск и graceful shutdown.
1. Реализация через BackgroundService (рекомендуется для большинства задач):
public class TimedBackgroundService : BackgroundService
{
private readonly ILogger<TimedBackgroundService> _logger;
public TimedBackgroundService(ILogger<TimedBackgroundService> logger)
{
_logger = logger;
}
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
_logger.LogInformation("Timed Background Service запущен.");
// Периодическая фоновая задача
using var timer = new PeriodicTimer(TimeSpan.FromSeconds(5));
try
{
while (await timer.WaitForNextTickAsync(stoppingToken))
{
// Работа, выполняемая каждые 5 секунд
_logger.LogInformation("Выполняется фоновая задача в: {time}", DateTimeOffset.Now);
// await DoWorkAsync(stoppingToken);
}
}
catch (OperationCanceledException)
{
_logger.LogInformation("Timed Background Service остановлен.");
}
}
}
2. Регистрация в DI-контейнере:
// В файле Program.cs
builder.Services.AddHostedService<TimedBackgroundService>();
Ключевые преимущества этого подхода:
- Управление жизненным циклом: Сервис автоматически запускается при старте приложения и останавливается при его завершении.
- Graceful Shutdown: Параметр
stoppingTokenавтоматически отменяется при попытке остановки хоста, давая сервису время на корректное завершение операций. - Внедрение зависимостей: Полноценная поддержка DI, можно инжектировать любые зарегистрированные сервисы (репозитории, клиенты API, логгеры).
- Асинхронность: Базовая реализация асинхронна.
Альтернативы и когда их использовать:
IHostedService(прямая реализация): Если нужен более тонкий контроль над процессом запуска/остановки.- Специализированные библиотеки:
- Hangfire: Для сложных фоновых заданий с планировщиком, повторными попытками, панелью мониторинга.
- Quartz.NET: Для высокоточного планирования по cron-выражениям.
TimerилиTask.Runв контроллере: Антипаттерн. Задачи могут быть потеряны при перезапуске приложения, сложно управлять, нет graceful shutdown.
Важные моменты для BackgroundService:
- Переопределяйте
ExecuteAsyncдля основной логики. - Всегда используйте переданный
stoppingTokenдля проверки отмены в долгих циклах или асинхронных операциях. - Обрабатывайте исключения внутри сервиса, чтобы одно упавшее фоновое задание не привело к падению всего хоста приложения.
Ответ 18+ 🔞
Ну, слушай, тут народ постоянно спрашивает, как бы им такую штуку запилить, чтобы что-то там на фоне тикало само по себе, типа отправки писем или чистки базы. И знаешь, какие дикие костыли иногда предлагают? Прям волосы дыбом, ей-богу.
Так вот, есть нормальный, каноничный способ, который все умные дядьки используют — это BackgroundService. Это такая удобная абстракция, она уже встроена в сам движок приложения, и с ней всё работает как часы: запускается с программой, останавливается с ней же, и зависимости туда можно запихнуть любые. Красота, а не подход.
Смотри, как это выглядит на практике:
public class TimedBackgroundService : BackgroundService
{
private readonly ILogger<TimedBackgroundService> _logger;
public TimedBackgroundService(ILogger<TimedBackgroundService> logger)
{
_logger = logger;
}
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
_logger.LogInformation("Timed Background Service запущен.");
// Таймер, который будет будить нас каждые 5 секунд
using var timer = new PeriodicTimer(TimeSpan.FromSeconds(5));
try
{
// Крутимся в цикле, пока приложение живо
while (await timer.WaitForNextTickAsync(stoppingToken))
{
// Вот тут делаем свою полезную работу
_logger.LogInformation("Выполняется фоновая задача в: {time}", DateTimeOffset.Now);
// await DoWorkAsync(stoppingToken);
}
}
catch (OperationCanceledException)
{
// Нас вежливо попросили завершиться
_logger.LogInformation("Timed Background Service остановлен.");
}
}
}
А чтобы эта магия заработала, нужно всего одну строчку добавить:
// В файле Program.cs
builder.Services.AddHostedService<TimedBackgroundService>();
Вот и вся магия, блядь. Сервис встроится в жизненный цикл, и когда приложение будут выключать, ему передадут специальный сигнал (stoppingToken) — это чтобы он мог не обрывать всё на полуслове, а аккуратно завершить текущую итерацию. Очень цивилизованно.
А теперь, блядь, что НЕ надо делать, а то руки оторву:
TimerилиTask.Runвсунуть прямо в контроллер — это пиздец какой антипаттерн. Приложение перезапустится, а твои таймеры повиснут в памяти как неприкаянные, или вообще все задачи похерятся. Никакого graceful shutdown, никакого контроля. Делать так — это себя не уважать.- Писать свою сложную систему потоков — зачем, если всё уже придумано? Только времени потратишь дохуя, а потом ещё и баги ловить.
Короче, запомни: для 95% задач хватит BackgroundService. Если нужна адская сложность с расписанием по крону, повторными попытками и мониторингом — тогда смотри в сторону Hangfire или Quartz.NET. Но для начала бери BackgroundService — не прогадаешь.
Главное, не забудь токен (stoppingToken) в свои долгие операции прокидывать и исключения внутри ловить, чтобы из-за одной ерунды весь сервис не лег.