Hangfire — планировщик задач для .NET

    Hangfire design
    Изображение с hangfire.io

    Hangfire — многопоточный и масштабируемый планировщик задач, построенный по клиент-серверной архитектуре на стеке технологий .NET (в первую очередь Task Parallel Library и Reflection), с промежуточным хранением задач в БД. Полностью функционален в бесплатной (LGPL v3) версии с открытым исходным кодом. В статье рассказывается, как пользоваться Hangfire.

    План статьи:


    Принципы работы


    В чем суть? Как вы можете видеть на КДПВ, которую я честно скопировал из официальной документации, процесс-клиент добавляет задачу в БД, процесс-сервер периодически опрашивает БД и выполняет задачи. Важные моменты:
    • Всё, что связывает клиента и сервера — это доступ к общей БД и общим сборкам, в которых объявлены классы-задачи.
    • Масштабирование нагрузки (увеличение количества серверов) — есть!
    • Без БД (хранилища задач) Hangfire не работает и работать не может. По-умолчанию поддерживается SQL Server, есть расширения для ряда популярных СУБД. В платной версии добавляется поддержка Redis.
    • В качестве хоста для Hangfire может выступать что угодно: ASP.NET-приложение, Windows Service, консольное приложение и т.д. вплоть до Azure Worker Role.

    С точки зрения клиента, работа с задачей происходит по принципу «fire-and-forget», а если точнее — «добавил в очередь и забыл» — на клиенте не происходит ничего, помимо сохранения задачи в БД. К примеру, мы хотим выполнить метод MethodToRun в отдельном процессе:
    BackgroundJob.Enqueue(() => MethodToRun(42, "foo"));

    Эта задача будет сериализована вместе со значениями входных параметров и сохранена в БД:
    {
        "Type": "HangClient.BackgroundJobClient_Tests, HangClient, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null",
        "Method": "MethodToRun",
        "ParameterTypes": "(\"System.Int32, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089\",\"System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089\")",
        "Arguments": "(\"42\",\"\\\"foo\\\"\")"
    }

    Данной информации достаточно, чтобы вызвать метод MethodToRun в отдельном процессе через Reflection, при условии доступа к сборке HangClient, в которой он объявлен. Естественно, совершенно необязательно держать код для фонового выполнения в одной сборке с клиентом, в общем случае схема зависимостей такая:
    module dependency
    Клиент и сервер должны иметь доступ к общей сборке, при этом для встроенного веб-интерфейса (о нем чуть ниже) доступ необязателен. При необходимости возможно заменить реализацию уже сохраненной в БД задачи — путем замены сборки, на которую ссылается приложение-сервер. Это удобно для повторяемых по расписанию задач, но, конечно же, работает при условии полного совпадения контракта MethodToRun в старой и новой сборках. Единственное ограничение на метод — наличие public модификатора.
    Необходимо создать объект и вызвать его метод? Hangfire сделает это за нас:
     BackgroundJob.Enqueue<EmailSender>(x => x.Send(13, "Hello!"));

    И даже получит экземпляр EmailSender через DI-контейнер при необходимости.

    Развернуть сервер (например в отдельном Windows Service) проще некуда:
    public partial class Service1 : ServiceBase
    {
        private BackgroundJobServer _server;
    
        public Service1()
        {
            InitializeComponent();
            GlobalConfiguration.Configuration.UseSqlServerStorage("connection_string");
        }
    
        protected override void OnStart(string() args)
        {
            _server = new BackgroundJobServer();
        }
    
        protected override void OnStop()
        {
            _server.Dispose();
        }
    }

    После старта сервиса наш Hangfire-сервер начнет подтягивать задачи из БД и выполнять их.

    Необязательным для использования, но полезным и очень приятным является встроенный web dashboard, позволяющий управлять обработкой задач:

    dashboard

    Внутренности и возможности Hangfire-сервера


    Прежде всего, сервер содержит свой пул потоков, реализованный через Task Parallel Library. А в основе лежит всем известный Task.WaitAll (см. класс BackgroundProcessingServer).

    Горизонтальное масштабирование? Web Farm? Web Garden? Поддерживается:
    You don’t want to consume additional Thread Pool threads with background processing – Hangfire Server uses custom, separate and limited thread pool.
    You are using Web Farm or Web Garden and don’t want to face with synchronization issues – Hangfire Server is Web Garden/Web Farm friendly by default.

    Мы можем создать произвольное количество Hangfire-серверов и не думать об их синхронизации — Hangfire гарантирует, что одна задача будет выполнена одним и только одним сервером. Пример реализации — использование sp_getapplock (см. класс SqlServerDistributedLock).
    Как уже отмечалось, Hangfire-сервер не требователен к процессу-хосту и может быть развернут где угодно от Console App до Azure Web Site. Однако, он не всемогущ, поэтому при хостинге в ASP.NET следует учитывать ряд общих особенностей IIS, таких как process recycling, авто-старт (startMode=«AlwaysRunning» ) и т.п. Впрочем, документация планировщика предоставляет исчерпывающую информацию и на этот случай.
    Кстати! Не могу не отметить качество документации — оно выше всяких похвал и находится где-то в районе идеального. Исходный код Hangfire окрыт и качественно оформлен, нет никаких препятствий к тому, чтобы поднять локальный сервер и походить по коду отладчиком.

    Повторяемые и отложенные задачи


    Hangfire позволяет создавать повторяемые задачи с минимальным интервалом в минуту:
    RecurringJob.AddOrUpdate(() => MethodToRun(42, "foo"), Cron.Minutely);

    Запустить задачу вручную или удалить:
    RecurringJob.Trigger("task-id");
    RecurringJob.RemoveIfExists("task-id");

    Отложить выполнение задачи:
    BackgroundJob.Schedule(() => MethodToRun(42, "foo"), TimeSpan.FromDays(7));

    Создание повторяющейся И отложенной задачи возможно при помощи CRON expressions (поддержка реализована через проект NCrontab). К примеру, следующая задача будет выполняться каждый день в 2:15 ночи:
    RecurringJob.AddOrUpdate("task-id", () => MethodToRun(42, "foo"), "15 2 * * *");


    Микрообзор Quartz.NET


    Рассказ о конкретном планировщике задач был бы неполон без упоминания достойных альтернатив. На платформе .NET таковой альтернативой является Quartz.NET — порт планировщика Quartz из мира Java. Quartz.NET решает схожие задачи, как и Hangfire — поддерживает произвольное количество «клиентов» (добавление задачи) и «серверов» (выполнение задачи), использующих общую БД. Но исполнение разное.
    Мое первое знакомство с Quartz.NET нельзя было назвать удачным — взятый из официально GitHub-репозитория исходный код просто не компилировался, пока я вручную не поправил ссылки на несколько отсутствующих файлов и сборок (disclaimer: просто рассказываю, как было). Разделения на клиентскую и серверную часть в проекте нет — Quartz.NET распространяется в виде единственной DLL. Для того, чтобы конкретный экземляр приложения позволял только добавлять задачи, а не исполнять их — необходимо его настроить.
    Quartz.NET полностью бесплатен, «из коробки» предлагает хранение задач как in-memory, так и с использованием многих популярных СУБД (SQL Server, Oracle, MySQL, SQLite и т.п.). Хранение in-memory представляет собой по-сути обычный словарь в памяти одного единственного процесса-сервера, выполняющего задачи. Реализовать несколько процессов-серверов становится возможным только при сохранении задач в БД. Для синхронизации, Quartz.NET не полагается на специфичные особенности реализации конкретной СУБД (те же Application Lock в SQL Server), а использует один обобщенный алгоритм. К примеру, путем регистрации в таблице QRTZ_LOCKS гарантируется единовременная работа не более чем одного процесса-планировщика с конкретным уникальным id, выдача задачи «на исполнение» осуществляется простым изменением статуса в таблице QRTZ_TRIGGERS.

    Класс-задача в Quartz.NET должен реализовывать интерфейс IJob:
    public interface IJob
    {
        void Execute(IJobExecutionContext context);
    }

    С подобным ограничением, очень просто сериализовать задачу: в БД хранится полное имя класса, что достаточно для последующего получения типа класса-задачи через Type.GetType(name). Для передачи параметров в задачу используется класс JobDataMap, при этом допускается изменение параметров уже сохраненной задачи.
    Что касается многопоточности, то Quartz.NET использует классы из пространства имен System.Threading: new Thread() (см. класс QuartzThread), свои пулы потоков, синхронизация через Monitor.Wait/Monitor.PulseAll.
    Немалой ложкой дегтя является качество официальной документации. К примеру, вот материал по кластеризации: Lesson 11: Advanced (Enterprise) Features. Да-да, это всё, что есть на официальном сайте по данной теме. Где-то на просторах SO встречался фееричный совет просматривать также гайды по оригинальному Quartz, там тема раскрыта подробнее. Желание разработчиков поддерживать похожее API в обоих мирах — Java и .NET — не может не сказываться на скорости разработки. Релизы и обновления у Quartz.NET нечасто.
    Пример клиентского API: регистрация повторяемой задачи HelloJob.
    IScheduler scheduler = GetSqlServerScheduler();
    scheduler.Start();
    
    IJobDetail job = JobBuilder.Create<HelloJob>()
        .Build();
    
    ITrigger trigger = TriggerBuilder.Create()
        .StartNow()
        .WithSimpleSchedule(x => x
        .WithIntervalInSeconds(10)
        .RepeatForever())
        .Build();
    
    scheduler.ScheduleJob(job, trigger);

    Основные характеристики двух рассмотренных планировщиков сведены в таблицу:
    Характеристика Hangfire Quartz.NET
    Неограниченное количество клиентов и серверов Да Да
    Исходный код github.com/HangfireIO github.com/quartznet/quartznet
    NuGet-пакет Hangfire Quartz
    Лицензия LGPL v3 Apache License 2.0
    Где хостим Web, Windows, Azure Web, Windows, Azure
    Хранилище задач SQL Server (по-умолчанию), ряд СУБД через расширения, Redis (в платной версии) In-memory, ряд БД (SQL Server, MySQL, Oracle...)
    Реализация многопоточности TPL Thread, Monitor
    Web-интерфейс Да Нет. Планируется в будущих версиях.
    Отложенные задачи Да Да
    Повторяемые задачи Да (минимальный интервал 1 минута) Да (минимальный интервал 1 миллисекунда)
    Cron Expressions Да Да

    UPDATE: Как справедливо заметил ShurikEv в комментариях, web-interface для Quartz.NET существует: github.com/guryanovev/CrystalQuartz

    Про (не)нагрузочное тестирование


    Необходимо было проверить, как справится Hangfire с большим количеством задач. Сказано-сделано, и я написал простейшего клиента, добавляющего задачи с интервалом в 0,2 с. Каждая задача записывает строку с отладочной информацией в БД. Поставив на клиенте ограничение в 100К задач, я запустил 2 экземпляра клиента и один сервер, причем сервер — с профайлером (dotMemory). Спустя 6 часов, меня уже ожидало 200К успешно выполненных задач в Hangfire и 200К добавленных строк в БД. На скриншоте приведены результаты профилирования — 2 снимка состояния памяти «до» и «после» выполнения:
    snapshots
    На следующих этапах работало уже 20 процессов-клиентов и 20 процессов-серверов, а время выполнения задачи было увеличено и стало случайной величиной. Вот только на Hangfire это не отражалось вообще никак:
    dashboard-2kk

    Выводы. Опрос.


    Лично мне понравился Hangfire. Бесплатный, открытый продукт, сокращает расходы на разработку и поддержку распределенных систем. Используете ли вы что-нибудь подобное? Приглашаю принять участие в опросе и рассказать свою точку зрения в комментариях.

    Только зарегистрированные пользователи могут участвовать в опросе. Войдите, пожалуйста.

    Какие планировщики задач вы используете при разработке на .NET?

    Поделиться публикацией

    Похожие публикации

    Комментарии 49

      +4
      Было бы интересно узнать о практическом применение таких менеджеров? Можете поделиться, в каких ситуациях вам приходилось их использовать?
        0
        Немного абстрактный пример — есть диспетчер, порождающий задачи и большое количество независимых друг от друга обработчиков, получающих атомарные задачи из очереди. При использовании таких менеджеров не нужно писать собственный сервис, раздающий задачи, что экономит довольно много времени.
          0
          Использовал Hangfire для управления задачами клиентских парсеров инстаграмма и для запуска всяческих процессов. Приложение на asp mvc.
            0
            (EN) -> Such as calculation schedulers, whereby one calculation depends on the completion of another calculation.

            Such calculations could be for inserting aggregated data into analytics database etc. We discovered Quartz.NET and resolved this problem, we can pool all the calculations in our calc engine and they are ordered with a 'manual' inheritance tree via `Quartz.NET`.

            Problem exists with thread failing, but that's another story.

            Sorry for no RU.
              0
              Например, выполнение задач по-расписанию (загрузка новых данных, тяжелые скрипты по ночам). Или перенос с клиента в отдельный какой-либо ресурсоемкой задачи.
                0
                В бизнес-процессах повсеместно — раз в сутки скачать/отправить данные, проверить наличие данных в смежной системе, наличие информации от клиентов и так далее.
                  0
                  Есть сервис электронного документооборота DocuSign
                  У них есть систему нотификации о произошедших событиях, которая рассылает оповещения по заранее сконфигурированным url
                  Однако эта функция доступна только для пользователей с самой дорогой лицензией.
                  Если вы создаёте сервис, который интегрируется с DocuSign, то для пользователей с отключённой функцией оповещения вам придётся запрашивать какие данные изменились каждые 15 минут.
                  Таким образом после того, как пользователь в вашем сервисе авторизовался в DocuSign — вы с помощью HangFire можете поставить повторяющуюся каждые 15 минут задачу на опрос изменений
                    0
                    В веб-приложениях постоянно приходится отдавать длительные задачи на выполнение в фоне, чтобы не заставлять пользователя ждать. Различные рассылки, импорт из больших XML, CSV файлов, создание архивов, отчетов и прочие вещи – в общем все то, что выполняется от одной секунды до нескольких часов (или имеет к этому склонность).
                    0
                    Мы у себя используем RabbitMQ — в Nugget есть отличный клиент для .Net, разворачивается под linux за 3 клика, в win за… 4. Думаю, его можно добавить в опрос в качестве варианта ответа.
                      +1
                      RabbitMQ это не планировщик. А средство доставки.
                        0
                        Да, прошу прощения, глаза зацепились за построение очереди. Тогда присоединяюсь к вопросу выше — было бы интересно узнать для каких задач нужен именно планировщик, а не система обмена сообщениями?
                          0
                          У нас на работе планировщик используется для периодического (от каждые 15 минут, до ежедневного или ежемесячного) сбора информации с различных источников (почта, ftp, api сайтов и т.д.).
                          Фактически куча ботов, которые умеют собираться информацию, затем боты которые обрабатывают и сохраняют.
                          Тоже самое работает и в обратном направлении, т.е. отправка.
                          Автоматизация повторяющихся действий, облегчение работы людей-менеджеров.
                            0
                            Чтобы использовать любую очередь сообщений для обработки задач, приходится принимать множество дополнительных решений – что будем хранить в очереди, как будем сериализовывать, сколько потоков на это дело выделить, что делать с исключениями, с неверными сообщениями, как это все мониторить, и т.д.

                            Вот когда реализуем все эти решения в коде, тогда и можно будет сказать, что построили систему обработки задач.
                              0
                              Еще одним пример — организация ETL процесса, если нужно что-то более кастомное чем SSIS.
                          +2
                          Хардкод конкретного метода конкретного класса с конкретным списком аргументов в конкретном порядке — лёгкий путь получить проблемы при обновлении системы. Почему-то разработчики подобных решений об этом не думают.
                            0
                            Возможный workaround: для повторяемых/отложенных задач хранить их id, далее при обновлении системы заменять задачи в БД новой версией.
                            Безусловно, проблема есть, но она решаема в разумные сроки.
                              0
                              UPD: вариантов решений вообще много, вплоть до переключения Hangfire-серверов на новый экземпляр базы данных Hangfire при апдейте системы, не прекращая поддержку старого, что логично.
                                0
                                Пока не столкнетесь, не оцените. У меня так в продуктиве однажды сдохли задачи, пусть и с другой системой хранений. Конвертировать такие сериализованные значения неудобно и даже не всегда можно, иногда старые записи должны работать по старому.
                                Просто имейте это в виду.
                              +1
                              Можно, и даже нужно, использовать интерфейсы и создавать на их основе фоновые задачи: BackgroundJob.Enqueue<IMyInterface>(x => x.SomeMethod()), и делать с реализацией что угодно. Главное интерфейсы не трогать, они любят когда нежно. А все изменения можно вносить через создание нового интерфейса (IMyInterfaceV2 и пр.).

                              Сериализация всегда порождает проблемы с обратной совместимостью, тут никуда не денешься. Даже с NuGet-пакетами та же проблема, когда вносятся breaking changes, и ломаются все зависимые пакеты.
                              0
                              Вообще-то hangfire не такой уж и бесплатный. У него есть Pro модификация в которой сейчас собственно и идет разработка, а бесплатный вариант не развивается.
                              Мы можем создать произвольное количество Hangfire-серверов и не думать об их синхронизации — Hangfire гарантирует, что одна задача будет выполнена одним и только одним сервером. Пример реализации — использование sp_getapplock (см. класс SqlServerDistributedLock).

                              Не гарантирует. Он (при организации расписания в sql) скрывает задачу из очереди на выполнение на 15 минут (по умолчанию, настраивается) а потом она появляется снова в очереди и может быть подхвачена другим сервером (или даже тем же самым). В результате хангфаер можно использовать только как планировщик задач, а управлять транзакционностью и т.п. приходится все равно снаружи. Вот и получается, что профит относительно quartz.net невелик.

                              Впрочем в качестве плюса можно отметить, что hangfire хорошо встраивается в asp.net приложения, не нужно следить за бекграунд потоками, в случае рестарта хоста корректно шлются завершающие сигналы во все запущенные потоки.
                                0
                                Впрочем в качестве плюса можно отметить, что hangfire хорошо встраивается в asp.net приложения, не нужно следить за бекграунд потоками, в случае рестарта хоста корректно шлются завершающие сигналы во все запущенные потоки.


                                Можно здесь подробней? Не совсем понятно какой тип задач в рамках ASP.NET сервера можно эффективно решать scheduler'om который привязан к таймеру, а не веб-запросу в качестве тригера.
                                К тому же Hangfire не имеет поддержки асинхронности, как я понимаю и на каждую задачу создает блокирующий long running background поток.
                                Зачем все это в хост веб приложения встраивать?
                                  0
                                  Можно здесь подробней? Не совсем понятно какой тип задач в рамках ASP.NET сервера можно эффективно решать scheduler'om который привязан к таймеру, а не веб-запросу в качестве тригера.

                                  Если делать отдельный сервис для бекграунд задач, то появляется лишняя единица развертывания. Во многих случаях это не оправданно. А типовые бекграунд задачи — посылать уведомления по расписанию или что-то подобное.
                                  Зачем все это в хост веб приложения встраивать?

                                  Я, честно говоря, последнее время считаю, что не нужно эти задачи встраивать в веб приложение и закладываю в архитектуру сразу отдельный сервис для бекграунд задач. Но это архитектуру усложняет и многие предпочитают хостить бекграунд задачи прямо в ASP.net приложении.
                                    0
                                    Простой планировщик не усложняющий архитектуру как-то слабо вяжется с распределенной системой имеющей persistance независимый от applicaton pool с очередью в базе в виде сериализованных .NET абстракций.
                                      0
                                      Ну так отдельный модуль, сепарированный, не требующий поддержки, легко встраиваемый. Если все это правда — он почти не усложняет архитектуру приложения, потому что его внутренняя архитектура никак не влияет на архитектуру приложения.
                                  0
                                  Вы хотите сказать, что длительная однократная задача (например, добавленная через BackgroundJob.Enqueue) может быть выполнена многократно? Как это воспроизвести? В моем случае, долговременные (2-4 часа) задачи выполняются строго 1 раз, иначе Hangfire было бы сложно воспринимать всерьез =)
                                    0
                                    Речь не совсем об этом. Не уверен что использовать в веб-сервере сторонние workers, хорошая идея. Даже несмотря на то, что там нету использования asp.net тред пула.
                                  +1
                                  Вы пишите, что Web-интерфейс у Quartz.NET отсутствует. Не соглашусь. Он есть. Например github.com/guryanovev/CrystalQuartz
                                  0
                                  Без БД (хранилища задач) Hangfire не работает и работать не может.

                                  Может: MemoryStorage.
                                  События можно создавать и обрабатывать в одном и том же процессе без каких-либо коннектов:
                                  GlobalConfiguration.Configuration.UseMemoryStorage();
                                  

                                  Масштабируемости и длительного хранения задач не будет, но иногда бывает удобно (например, удалять временные файлы с перепопытками в случае неудачи).
                                    0
                                    «И все-таки это хранилище!»
                                      0
                                      Безусловно.
                                      Я просто добавил, что Hangfire можно использовать в очень простом виде (без SQL, Redis и отдельных процессов обработчиков). Даже дашборд работает.
                                    0
                                    chumakov-ilya, а с Microsoft HPC Pack technet.microsoft.com/ru-ru/library/jj899572.aspx можно использовать для таких целей, возможности не сравнивали?
                                      0
                                      С этой технологией не знаком, ничего сказать не могу.
                                      +1
                                      Пользуюсь этим планировщиком около года, но только для мелких процессов которые нужно выполнять непосредственно на сервере приложения. Для реальных вычислительных процессов пользуюсь Azure Batch с автоматическим масштабированием (вычислительная мощность непосредственно на веб-сервере дорого обходится).

                                      Одна неприятная особенность hangfire — каждый heartbeat это запись в базу данных. Если ведётся аудит базы, то он разрастётся очень быстро и может стать почти неподъёмным. Ещё трудней, если приложение развёрнуто в десятке разных окружений и соответственно для каждого окружения своя база данных. Поэтому я сделал отдельную базу данных для hangfire и подключил к ней все приложения с уникальными идентификаторами. В руководствах это описывается нечасто, но отделяйте зёрна от плевел, впоследствии будет легче.
                                        0
                                        Hangfire умеет работать с распределенными транзакциями, обрабатывать один таск сутки или более? Ожидать сигнала извне на каком-то этапе? Есть условные переходы по шагам тасков? Можно ли вообще делать последовательность или иерархию шагов, которые надо выполнить?
                                          0
                                          Hangfire это все же не энтерпрайз шина, а пакет, встраивающий в приложение возможность управления задачами. Планировщик и несколько очередей выполнения задач.
                                            0
                                            Понятно, спасибо.
                                          0
                                          Интересно, можно ла сравнить «планировщик задач» с «моделью акторов»? на первый взгляд кажется, что есть большое пересечение, например с AKKA.NET

                                          Есть где то простой работающий пример установки и запуска простого теста на двух компьютерах для рассматриваемых планировщиков? без базы данных, без .ASP или windows сервисов?
                                            0
                                            Сравнить как равные альтернативы для решения одинаковых задач в общем нельзя.

                                            Ну к примеру Hangfire без базы данных не работает в принципе. В базе данных он содержит очередь задач, вместе с непосредственно задачами и прочими инфраструктурными данными. То есть реально в базу данных записывается путь к исполняемому методу, название метода, типы параметров, сами параметры.

                                            Абстрактный планировщик в вакууме может обойтись и без базы данных, но всё равно нужно будет где-то содержать расписание и историю исполнения.

                                            Планировщик задач служит для того, чтобы можно было составить расписание (я хочу исполнять метод X каждое воскресенье в полночь с вот такими параметрами; хочу исполнять метод y через 10 минут после нажатия кнопки; и т.д), чтобы можно было посмотреть какое расписание есть на данный момент, и чтобы можно было посмотреть какие процессы уже исполнены и когда.

                                            Акторы это грубо говоря модули, которые исполняются параллельно и бывает так, что взаимодействуют друг с другом (один актор, когда ему нужно, отсылает сообщение другому и тот другой обрабатывает сообщение в соответствии со своей логикой, не останавливая работу первого). Таким образом в процессе разработки планировщика задач вы скорее всего будете использовать модель акторов, это имеет смысл, это удобно.

                                            Можно реализовать одинаковую функциональность с планировщиком и без планировщика, а можно сделать свой планировщик не зная что этот девайс так называется.
                                              0
                                              Просто в начале поста написано
                                              С точки зрения клиента, работа с задачей происходит по принципу «fire-and-forget», а если точнее — «добавил в очередь и забыл» — на клиенте не происходит ничего…

                                              Вот я и подумал, что это похоже на много чего, в том числе и AKKA.NET.

                                              Теперь (в том числе и после вашего ответа) я понимаю, что тема поста все-таки инструменты, которые позволюят что то выполнить по некому событию.

                                              Но есть тоглда другой вопрос: если мы заносим нечто, что должно выполняться раз в день и это нечно «восстанавливается» по Reflection, то получается любой апдейт части системы требует перезапуск очереди, что бы поместить в нее опять все те же задачи? Ведь типы могут измениться.
                                              Я понимаю, с одной стороны это сильно облегчает программирование: любой метод засунул в «расписание» и он будет выполняться. С другой стороны, этот метод не может «долго» храниться.
                                                0
                                                Да, бывают такие случаи, когда вы занесёте в планировщик задачу с рефлектом, а потом обновите код программы. Обработка ошибок должна быть предусмотрена непосредственно в планировщике. В худшем случае тред будет оборван, а задача “зависнет” с каким-нибудь статусом. В лучшем случае, планировщик сможет словить ошибку, записать в результат, и перезапустить задачу максимум N раз (согласно настройкам планировщика).

                                                Был у меня случай когда я использовал сущность из EF в качестве аргумента для задачи, отправленной в планировщик. Естественно, после обновления новый рантайм, и стало быть новые типы (особенность EF). Hangfire словил исключение, записал его, и так пять раз кряду, а потом забил, записав задачу как Failed. Мне, соответственно пришлось выяснить почему не работает функционал (спасибо тестерам), ну вот и закрутилось колесо дебага.

                                                В общем-то “облегчает программирование” тут ни при чём. Ясное дело, что взять готовый инструмент и прикрутить его к вашему приложению — легче, чем написать инструмент с нуля или даже собрать его из каких-то модулей, но не в этом суть самого инструмента.

                                                Вот вам юз-кейс:
                                                Вы администратор. Ваша сеть расширилась и уже влом запускать бэкапы вручную. Вы пишете скрипт, который обращается в сеть, находит на данный момент включенные в сеть машины (у вас ведь могут быть и кочевые ноутбуки, и что угодно ещё), и отправляет их бэкапиться на ваш бэкап-сервер. Потом вы устанавливаете скрипт в крон бэкап серверу и вот у вас есть запланированная задача. Крон — это планировщик задач, он не облегчает жизнь разработчику ОС, он облегчает жизнь конечному пользователю, в данном случае администратору системы.

                                                Планировщики задач существуют в большей степени не для того, чтобы “выполнить по некому событию”, а для того, чтобы “выполнить тогда, когда конечный пользователь укажет”. Иногда в бизнес-логике приложения бывают ситуации, когда это предрешено самой бизнес-логикой. Ну к примеру, у вас есть генератор отчётов и настройка времени действительности отчёта. Допустим отчёт действителен только в течение двух недель, а через две недели он теряет смысл. В приложении есть процедура генерации отчёта и процедура удаления отчёта. Генерация отчёта предусматривает занесение в планировщик задачи удаления конкретно этого отчёта через две недели. Процедура занесения задачи в планировщик предусмотрена разработчиком, но точное время исполнения назначает конечный пользователь при запросе нового отчёта. Всё для того, чтобы список результатов не раздувался за счёт устарелых данных. То есть опять же для облегчения жизни конечному пользователю.

                                                В данном контесте «fire-and-forget» относится именно к пользователю. Он просто жмёт кнопку. Ему не нужно ждать, смотреть на полоску “loading” внизу окна браузера, пока метод вернёт результат. Метод исполнится тогда, когда для этого будет свободная мощность, примерно в то время, в которое положено, и пользователь об этом будет оповещён (с поправкой на дизайн интерфейса и логики метода). А пользователь может работать дальше и заниматься своими делами.
                                                  0
                                                  Я имею ввиду, что reflection позволяет не заморачиваться с построением специальных классов (которые наследуются от специальных интерфейсов) и легко и быстро добавлять «задачи» в очередь. Но, на мой взгляд, эти «задачи» могут быть актуальны только а рамках одного текущего билда программы. Хранить же «задачи» и использовать их в разных билдах выглядит очень опасно. Т.е. мы как бы не определяем API и значит не можем ни на что рассчитывать в следующем билде.

                                                    0
                                                    Так и есть, так и случается. Опасно, но не очень. В худшем случае — не будет исполнено. Ну если особый талант есть, то можно сделать так чтобы исполнить с ошибками, и тут начинается самая веселуха. Можно и данные напортить. Многое зависит от бизнес-логики приложения и от архитектуры. На старые задачи рассчитывать нельзя, а на новые — вполне можно. Естественно, можно предусматривать закрытие старых задач при создании нового рантайма, оповещение пользователей, и прочую инфраструктуру. Для того и есть архитектор приложения, чтобы этими вопросами заниматься, а разработчики — его информировать и внедрять эти решения. Да и в любом вопросе рефлексии идеальных решений нет, зыбкая область, нужно быть готовым ко всему.
                                                      0
                                                      Кмк, риск не больше, чем изменение схемы БД или контракта веб-сервиса. К такому нужно быть готовым, но и злоупотреблять не следует.
                                                    0
                                                    Был у меня случай когда я использовал сущность из EF в качестве аргумента для задачи, отправленной в планировщик. Естественно, после обновления новый рантайм, и стало быть новые типы (особенность EF). Hangfire словил исключение, записал его, и так пять раз кряду, а потом забил, записав задачу как Failed. Мне, соответственно пришлось выяснить почему не работает функционал (спасибо тестерам), ну вот и закрутилось колесо дебага.


                                                    Вообще-то автор хангфаера явно и четко пишет, что не рекомендуется использовать в качестве аргументов задачи объекты сложных типов. То есть по хорошему вам нужно было передать в него идентификатор сущности а саму сущность достать из бд в рамках задачи.
                                                      0
                                                      По-хорошему и переделал. Одно дело “не рекомендуется” и совсем другое “не получится, даже если очень захочешь”. Если честно, то я как старший должен был изучить документацию прежде, чем оставлять внедрение коллегам, но тут уж как вышло. Танцы на граблях хорошему учат.
                                              0
                                              Просто в начале поста написано
                                              С точки зрения клиента, работа с задачей происходит по принципу «fire-and-forget», а если точнее — «добавил в очередь и забыл» — на клиенте не происходит ничего…

                                              Вот я и подумал, что это похоже на много чего, в том числе и AKKA.NET.

                                              Теперь (в том числе и после вашего ответа) я понимаю, что тема поста все-таки инструменты, которые позволюят что то выполнить по некому событию.

                                              Но есть тоглда другой вопрос: если мы заносим нечто, что должно выполняться раз в день и это нечно «восстанавливается» по Reflection, то получается любой апдейт части системы требует перезапуск очереди, что бы поместить в нее опять все те же задачи? Ведь типы могут измениться.
                                              Я понимаю, с одной стороны это сильно облегчает программирование: любой метод засунул в «расписание» и он будет выполняться. С другой стороны, этот метод не может «долго» храниться.

                                              Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

                                              Самое читаемое