Найти в Дзене
Архитектура на .NET

Выполнение фоновых задач в ASP.NET приложениях

При разработке веб-сервисов зачастую возникают задачи, которые следует выполнять не в рамках обработки запросов, а в фоновом режиме. Примерами таких задач могут быть: В каждом из этих случаев удобно иметь некий постоянно либо периодически выполняющийся код, который отвечал бы за свою отдельную задачу. В asp.net доступны несколько способов создания подобных фоновых сервисов: Использование IHostedService Интерфейс IHostedService определяет два метода для реализации: Если вы реализовали интерфейс IHostedService и зарегистрировали его в DI через AddHostedService<>(), его метод StartAsync() будет вызван в процессе запуска приложения до того, как будет запущен хост, обрабатывающий входящие http-запросы. Таким образом, в этом методе можно выполнить задачи, которые необходимо завершить до запуска приложения. Например, применить миграции. Если вы хотите реализовать задачу, выполняющуюся на протяжении всего времени работы приложения, то вам нужно сделать отдельную задачу (Task), сохранить её в
Оглавление

При разработке веб-сервисов зачастую возникают задачи, которые следует выполнять не в рамках обработки запросов, а в фоновом режиме.

Примерами таких задач могут быть:

  • Обновление кэшированных данных
  • Архивация устаревших записей
  • Подготовка отчётов
  • Формирование периодических уведомлений
  • Полное удаление сущностей из "корзины" по истечении определенного срока
  • и т.п.

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

В asp.net доступны несколько способов создания подобных фоновых сервисов:

  • Интерфейс IHostedService
  • Готовая реализация этого интерфейса в виде класса BackgroundService

Использование IHostedService

Интерфейс IHostedService определяет два метода для реализации:

  • StartAsync(CancellationToken)
  • StopAsync(CancellationToken)

Если вы реализовали интерфейс IHostedService и зарегистрировали его в DI через AddHostedService<>(), его метод StartAsync() будет вызван в процессе запуска приложения до того, как будет запущен хост, обрабатывающий входящие http-запросы. Таким образом, в этом методе можно выполнить задачи, которые необходимо завершить до запуска приложения. Например, применить миграции.

Если вы хотите реализовать задачу, выполняющуюся на протяжении всего времени работы приложения, то вам нужно сделать отдельную задачу (Task), сохранить её в свойстве вашего класса, реализующего интерфейс IHostedService, а в методе StartAsync только запустить её, и выйти из метода.

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

Метод StopAsync вызывается при мягкой остановке приложения, и из него можно закрыть все используемые ресурсы, если таковые были открыты. Переданный в метод cancellationToken сработает через 30 секунд (если используется таймаут по умолчанию).

Использование класса BackgroundService

В отличие от IHostedService, класс BackgroundService предоставляет отдельный абстрактный метод ExecuteAsync(CancellationToken), реализация которого уже может содержать код, который выполняется в течение всего времени работы приложения. По сути, этот метод должен возвращать ту самую Task, которую следовало бы создать вручную из метода StartAsync, реализуя интерфейс IHostedService напрямую.

Пример кода реализации BackgroundService рассмотрим чуть ниже.

Использование scoped-сервисов из фоновых потоков

Если необходимые сервисы (например, unit of work или репозиторий, содержащие DbContext EF Core) зарегистрированы в DI как scoped-сервисы, для использования их из фоновых потоков потребуется создавать свой отдельный scope (пример кода ниже).

Пример кода сервиса, выполняющего фоновую работу с репозиторием каждые 10 минут

-2

Выводы

Если вам нужно выполнить краткосрочную задачу до запуска хоста, используйте интерфейс IHostedService, и помещайте код напрямую в метод StartAsync().

Если вам нужно периодически выполнять определенные задачи во всё время жизни приложения, а не только при старте, используется класс BackgroundService, и переопределяйте метод ExecuteAsync(). Возвращаемый этим методом Task может быть долгоживущим, он не будет блокировать запуск приложения.