[Из песочницы] Как запустить фоновый процесс в Asp.net

Мне понадобилось запустить фоновый процесс в ASP.NET. Возник вопрос: как лучше это сделать? Немного погуглив в блоге SCOTT HANSELMAN, я нашел запись «How to run Background Tasks in ASP.NET». Статья не очень новая — 2014 года, но вполне актуальная, поэтому я решил перевести ее на русский язык.

Несколько лет назад Phil Haack написал великолепную статью об опасностях выполнения фоновых задач в ASP.NET. Он выделил три основных риска, связанных с запуском фонового процесса:
  1. Необработанное исключение в потоке, несвязанном с запросом, может снять процесс.
  2. Если запустить сайт в веб-ферме, то есть вероятность случайно завершить несколько экземпляров приложения, которое могло запустить несколько экземпляров одной и той же задачи одновременно.
  3. Домен приложения, в котором запущен сайт, по разным причинам может выгрузиться и снять фоновую задачу, запущенную в нем.

Конечно, можно написать собственный менеджер для управления фоновыми задачами. Но, вероятнее всего, Вы сделаете это неправильно. Никто не собирается оспаривать Ваши навыки разработчика. Просто создание подобного менеджера — это достаточно тонкая вещь. Да и зачем Вам это надо?

Есть множество отличных вариантов запуска задач в фоновом режиме. И не просто абстрактных методик, а готовых библиотек.

Какие-то ASP.NET приложения могут работать на Ваших собственных серверах под IIS, какие-то размещаться в Azure.

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

  • Основной: Hangfire или аналогичная библиотека, которую можно использовать для написания фоновых задач на Вашем собственном ASP.NET сайте.
  • Облачный: Azure WebJobs веб-задания Azure. Собственный сервис в Azure, который можно использовать для запуска фоновых задач вне Вашего веб-сайта с возможностью масштабирования нагрузки.
  • Расширенный: Веб-роль Azure в облачных службах. Сервис, позволяющий запускать фоновые задачи независимо от Вашего сайта, с возможностью масштабирования нагрузки и контролем выполнения.

В сети можно найти большое количество статей и видео о том, как использовать задачи Azure и как работают веб-роли в облачных службах Azure. Но не так много статей о том, как запускать фоновые задачи на собственном сервере.

WEBBACKGROUNDER


Как написано на сайте: «WebBackgrounder — это проверка концепции совместимого с веб-фермами менеджера фоновых задач, который должен работать только с веб-приложениями ASP.NET». Его код не менялся уже много лет, но NuGet пакет был скачан более полумиллиона раз.

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

Это явно библиотека не для всех случаев жизни. Но если во время работы ASP.NET приложения необходимо запускать всего одну задачу, то WebBackgrounder — все что Вам нужно.

using System;
using System.Threading;
using System.Threading.Tasks;
 
namespace WebBackgrounder.DemoWeb
{
    public class SampleJob : Job
    {
        public SampleJob(TimeSpan interval, TimeSpan timeout)
            : base("Sample Job", interval, timeout)
        {
        }
 
        public override Task Execute()
        {
            return new Task(() => Thread.Sleep(3000));
        }
    }
}

Добавленный в .NET 4.5.2 QUEUEBACKGROUNDWORKITEM


Можно сказать, что QueueBackgroundWorkItem был добавлен в .NET 4.5.2 в ответ на появление WebBackgrounder. Это не просто реализация «Task.Run». Это нечто большее:

QBWI управляет по расписанию задачами, которые должны запускаться в фоновом режиме, независимо от любого запроса. Разница с обычным элементом ThreadPool заключается в том, что ASP.NET автоматически отслеживает сколько рабочих элементов, зарегистрированных с помощью API, запущено в настоящий момент, а в случае выключения домена приложения среда выполнения ASP.NET пытается отсрочить его, давая возможность завершить работу запущенным фоновым задачам.

Необходимо учитывать, что среда выполнения ASP.NET может отсрочить выключение домена приложения всего на 90 секунд, давая возможность завершить задачи. Поэтому, если Вы не можете завершить задачи за это время, то вам необходимо использовать другие, более надежные средства.

API очень простое — используется «Func». Далее небольшой пример, который запускает фоновый процесс из контроллера MVC:

public ActionResult SendEmail([Bind(Include = "Name,Email")] User user)
{
    if (ModelState.IsValid)
    {
       HostingEnvironment.QueueBackgroundWorkItem(ct => SendMailAsync(user.Email));
       return RedirectToAction("Index", "Home");
    }
 
    return View(user);
}

FLUENTSCHEDULER


FluentScheduler — более продвинутый и сложный менеджер задач с fluent интерфейсом. С его помощью вы действительно можете управлять процессом запуска задачи.
using FluentScheduler;
 
public class MyRegistry : Registry
{
    public MyRegistry()
    {
        // Запуск задачи ITask через определенный интервал времени
        Schedule().ToRunNow().AndEvery(2).Seconds();
 
        // Запуск простой задачив определенный момент времени 
        Schedule(() => Console.WriteLine("Timed Task - Will run every day at 9:15pm: " + DateTime.Now)).ToRunEvery(1).Days().At(21, 15);
 
        // Запуск более сложного действия – запуск задачи немедленно и с месячным интервалом
        Schedule(() =>
        {
            Console.WriteLine("Complex Action Task Starts: " + DateTime.Now);
            Thread.Sleep(1000);
            Console.WriteLine("Complex Action Task Ends: " + DateTime.Now);
        }).ToRunNow().AndEvery(1).Months().OnTheFirst(DayOfWeek.Monday).At(3, 0);
    }
}

Также FluentScheduler поддерживает IoC и может легко подключаться с помощью Вашей любимой Dependency Injection библиотеки, просто реализуя его ITaskFactory интерфейс.

Необходимо отметить, что FluentScheduler может работать с .NET Core.

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

JobManager.JobException += (info) => Log.Fatal("An error just happened with a scheduled job: " + info.Exception);

QUARTZ.NET


Quartz.NET — это версия популярного фреймворка для Java, реализованного в .NET. Фреймворк активно развивается. Для создания фоновой задачи в Quartz используется интерфейс IJob с единственным методом Execute, который и надо реализовать.
using Quartz;
using Quartz.Impl;
using System;
 
namespace ScheduledTaskExample.ScheduledTasks
{
    public class JobScheduler
    {
        public static void Start()
        {
            IScheduler scheduler = StdSchedulerFactory.GetDefaultScheduler();
            scheduler.Start();
 
            IJobDetail job = JobBuilder.Create().Build();
 
            ITrigger trigger = TriggerBuilder.Create()
                .WithDailyTimeIntervalSchedule
                  (s =>
                     s.WithIntervalInHours(24)
                    .OnEveryDay()
                    .StartingDailyAt(TimeOfDay.HourAndMinuteOfDay(0, 0))
                  )
                .Build();
 
            scheduler.ScheduleJob(job, trigger);
        }
    }
}

Для запуска менеджера внутри Application_Start необходимо вызвать JobScheduler.Start (). Для начала работы можно прочитать Действия по расписанию и Quartz.NET (перевод Scheduled Tasks In ASP.NET With Quartz.Net).

Проект имеет довольно приличную документацию, которую стоит прочитать перед началом использования (как минимум, стоит просмотреть Tutorials for Developing with Quartz).

HANGFIRE


И последний в обзоре, но, безусловно, непоследний по функциональности, наиболее продвинутый из всех Hangfire. Это действительно очень продвинутый фреймворк для фоновых задач в ASP.NET. Опционально он может использовать Redis, SQL Server, SQL Azure, MSMQ или RabbitMQ для повышения надежности выполнения задач.

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

Одной из самых эффектных функций Hangfire является встроенная аналитическая панель, которая позволяет просматривать расписания, выполняющиеся задания, успешные и неуспешно завершенные задания.

Hangfire позволяет легко определить задачи типа «запустить и забыть», информация о которых будет храниться в базе данных:

BackgroundJob.Enqueue(() => Console.WriteLine("Fire-and-forget"));

Можно отсрочить выполнение задачи:
BackgroundJob.Schedule(() => Console.WriteLine("Delayed"), TimeSpan.FromDays(1));

Или запустить задачу в CRON стиле
RecurringJob.AddOrUpdate(() => Console.Write("Recurring"), Cron.Daily);

Работать с Hangfire очень удобно. Hangfire имеет хорошую документацию и обучающие руководства, основанные на реальных примерах.

Hangfire — это целая экосистема для работы с фоновыми задачами в ASP.NET приложениях.

Библиотеки доступны в виде открытых исходных кодов или Nuget пакетов.

Итоги (лично от себя)


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

Я уже знаю, что мне нужно запускать больше одного процесса, и работать процессы могут долго (ограничение в 90 секунд на завершение в QueueBackgroundWorkItem). FluentScheduler выглядит неплохо, но хотелось большего. Hangfire — отличное решение, но, вроде, сразу требует использования базы данных для хранения очереди задач. Да и не совсем там все бесплатно — есть и платная версия.

В итоге я пока выбрал Quartz.NET: вполне приличная документация, достаточное количество примеров, чтобы быстро начать использовать, а также расширяемый функционал, который можно добавлять по мере возрастания потребностей.

Если вы знаете другие библиотеки для запуска фоновых задач или имеете опыт решения подобных задач — делитесь в комментариях.

Комментарии (2)

  • 9 февраля 2017 в 18:01

    0

    Hangfire плох тем, что минимальный интервал минута там, в остальном больше всех понравился. В итоге пользуюсь Quartz.NET, но панель нужно ставить отдельно (CrystalQuartz), да и она просто отвратительна. К тому же стоит учесть, что с CrystalQuartz нужен первый толчок, так как запускаются все задачи уже иначе.
  • 9 февраля 2017 в 18:16

    0

    Да, кстати сравнительная таблица, пусть даже добавленная в перевод от себя, смотрелась бы отлично.

© Habrahabr.ru