Выполнение кода в фоне в ASP.NET при помощи Hangfire

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

Решают эту задачу все по-разному - кто-то создает свою службу Windows, кто-то запускает код по расписанию с помощью планировщика Windows. Есть ещё один прекрасный способ - библиотека Hangfire.

В основе идеи Hangfire лежит ключевое "fire-and-forget", что означает запустить код на выполнение и забыть - далее он выполнится в фоне без вашего участия. При этом вам не потребуются дополнительные инфраструктурные вмешательства (такие как Windows Service), что существенно упростит процесс развертывания. Hangfire подключается к приложению путем установки пакета из Nuget.

Подключение Hangfire к приложению

Для подключения Hangfire к приложению нужно установить пакет Hangfire из Nuget:

Install-Package HangFire

Данный пакет содержит базовую инфраструктуру Hangfire и хранилище для SQL Server. Hangfire использует внешнее хранилище данных для очередей для того, чтобы фоновые задачи не удалялись при перезапуске приложения. По умолчанию в качестве хранилища используется SQL Server, в платной Pro-версии есть возможность работать с Redis. Также я сделал свой провайдер для Mongo.

Для запуска Hangfire используется инфраструктура OWIN (отличное решение, с учетом задела на будущее), для конфигурации нужно выполнить метод UseHangfire:

public void Configuration(IAppBuilder app)
{
  app.UseHangfire(config =>
  {
    config.UseSqlServerStorage("<connection string or its name>");
    config.UseServer();
  });
}

Метод UseHangfire принимает в качестве аргумента метод для конфигурирования Hangfire. Здесь нужно обязательно использовать какой-либо из типов источников данных. Провайдер для SQL Server автоматически создаст нужные таблице в вашей базе данных, поэтому дополнительных действий тут не требуется - нужно только указать строку подключения SQL Server:

public void Configuration(IAppBuilder app)
{
  app.UseHangfire(config =>
  {
    config.UseSqlServerStorage(
        @"Data Source=.\SQLEXPRESS;Initial Catalog=TestApplication;Integrated Security=True");
    config.UseServer();
  });
}

Необходимые таблицы будут созданы при первом запуске приложения:

Аналогично работают провайдеры для Redis и Mongo — нужные коллекции создаются автоматически.

Этого достаточно для того, чтобы Hangfire заработал. Можно запустить приложение и вам будет доступен Dashboard от Hangifre по адресу /hangfire. Там содержаться все задачи — выполненные, запланированные, ошибочные и т.д.

Если требуется закрыть паролем этот Dashboard, то существует отдельный пакет - Hangfire.Dashboard.Authorization, который поддерживает встроенную аутентификацию ASP.NET, а также Basic Authentication. Не забывайте настроить HTTPS для вашего веб-приложения.

Такой конфигурации достаточно, чтобы потестировать Hangfire, но для продакшн-среды нужны некоторые дополнительные действия. Что именно нужно сделать зависит от типа хостинга вашего приложения, но в целом идея заключается в том, чтобы убедиться, что ASP.NET приложение не переходит в спящий режим. Подробнее это описано в официальной документации.

Запуск задач при помощи Hangfire

Hangfire имеет возможность запуска разных типов задач:

  • простая задача в фоне, которая запустится как можно скорее ("fire-and-forget")
  • задача, выполняемая с задержкой (например, через 1.5 часа после постановки в очередь)
  • задача, выполняемая периодически по заданному расписанию

При запуске задачи в Hangfire она помещается в очередь (в хранилище появляется соответствующая запись) и далее выполняется в фоне.

Для примера, создадим приложение, отправляющее email-ы. Почему это нужно делать в фоне? Дело в том, что скорость отправки сообщения зависит от конкретного SMTP-сервера. Эта задержка увеличивается, если отправлять несколько сообщений. Например, у меня были случаи, когда по запросу от пользователя нужно было рассылать более 10 сообщений - в этом случае задержка становится ощутимой. В случае с Hangfire мы просто ставим задачи по отправке email-сообщений в очередь и больше не задерживаем пользователя. Поэтому предпочтительнее обрабатывать такие задачи именно в фоне.

Создадим простейший сервис для отправки сообщения:

public static class EmailService
{
  public static void Send(string from, string to, string subject, string body)
  {
    using (var smtp = new SmtpClient())
    {
      // ...

      smtp.Send(from, to, subject, body);
    }
  }
}

Чтобы поставить задачу на отправку сообщения в очередь следует воспользоваться объектом BackgroundJob:

public ActionResult Send()
{
  BackgroundJob.Enqueue(() =>
    EmailService.Send("[email protected]", "[email protected]", "Subject", "Text"));

  return View();
}

Как только задачи будут поставлены в очередь, это будет видно в Hangfire Dashboard:

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