Czasochłonne operacje w ASP.NET – Hangfire

W jednym z poprzednich wpisów, pokazałem jak prawidłowo tworzyć wątki w tle w celu wykonania jakieś czasochłonnej operacji w ASP.NET. Pod wpisem, Michal Dymel zaproponował narzędzie o nazwie Hangfire.
Muszę przyznać, że framework jest bardzo prosty w użyciu i nie ma zbyt wiele zewnętrznych zależności. Od kilku tygodni korzystam z niego w jednym ze swoich projektów i nie miałem żadnych problemów z konfiguracją czy wdrążaniem rozwiązania w system produkcyjny.

Po kolei jednak… Hangfire służy do wykonywania czasochłonnych operacji w ASP.NET. Dostarcza zatem model “fire&forget”, czyli bardzo prostą implementację systemu kolejkowego. Jeśli zaplanowane zadanie niepowiedzie się (np. timeout), wtedy zostanie automatycznie kilkakrotnie razy powtarzane, w różnych odstępach czasu (tak jak w nServiceBus).
Wiemy, że IIS może zamknąć proces w dowolnym momencie, kończąc tym samym wszystkie zadania, które były wykonywane w tle. Dzięki Hangfire, nie musimy się martwić o wznawianie takiej operacji.

Zaplanowane zadania, przechowywane są w bazie danych, np. SQL Server czy Redis. Istnieje możliwość zaimplementowania własnego repozytorium, ale jest to dość skomplikowane.

Ponadto, Hangfire dostarcza bardzo przyjazny interfejs użytkownika. Instalacja jest bardzo prosta – wystarczy zainstalować poniższy pakiet NuGet:

 Install-Package HangFire

Następnie w Owin startup, umieszczamy:

    public class Startup
    {
        public void Configuration(IAppBuilder app)
        {
            GlobalConfiguration.Configuration
              .UseSqlServerStorage(@"Server=PIOTR\SQLEXPRESS;Database=HangfireDb;Trusted_Connection=True;");

            app.UseHangfireDashboard();
            app.UseHangfireServer();
        }
    }

Powyższy przykład bazuje na SQL Server jako repozytorium zadań. Hangfire sam stworzy wymaganą strukturę tabel. Po uruchomieniu aplikacja, baza danych zostanie zainicjalizowana następującymi tabelami:

hangfiredb

W celu zaplanowania zadania, które wykonuje się o określonym czasie wystarczy:

RecurringJob.AddOrUpdate("Nazwa zadania",() => Console.WriteLine("Zaplanowane zadanie...."),Cron.Minutely);

Zwykle powyższy kod umieszcza się w Startup. Możemy przekazać jako parametr dowolne wyrażenie CRON. Hangfire zadba, aby zadanie wykonywało się o określonych porach oraz zostało wznawiane w przypadku recycling dokonywanego przez IIS.

Możemy również dodać zadanie do kolejki,  jeśli chcemy, aby zostało wykonane tylko raz:

BackgroundJob.Enqueue(
    () => Console.WriteLine("Test"));

Dużym udogodnieniem jest dostarczenie UI w formie dashboard. Przechodząc na stronę /hangfire (np. http://localhost:63486/hangfire), zobaczymy diagram pokazujący liczbę wykonanych zadań w ciągu ostatniego tygodnia lub dnia:

dashboard

W zakładce “Recurring tasks” zobaczymy nasze zaplanowane zadania:

recurring_tasks

Mamy szybki i łatwy podgląd, kiedy ostatnio zadanie zostało wykonywane i z jaką częstotliwością będzie wykonywane.

Przechodząc do zakładki Jobs, zobaczymy listę dostępnych kolejek:

queues

Widzimy, że aktualnie mamy 9 zadań w “Succeeded”.  Hangfire zadba o tym, aby nie zapychać powyższych kolejek i każdego dnia niepotrzebne wpisy zostają usuwane. Przechodząc do jakiejkolwiek kolejki, możemy przyjrzeć się szczegółom wykonanemu zadaniowi:

task_sucess

Załóżmy, że nasze zadanie teraz będzie wyrzucało wyjątek:

    public class Startup
    {
        public void Configuration(IAppBuilder app)
        {
            GlobalConfiguration.Configuration
                .UseSqlServerStorage(@"Server=PIOTR\SQLEXPRESS;Database=HangfireDb;Trusted_Connection=True;");

            app.UseHangfireDashboard();
            app.UseHangfireServer();

            RecurringJob.AddOrUpdate("Nazwa zadania", () => SampleTask(), Cron.Minutely);
        }

        private static void SampleTask()
        {
            throw new NotImplementedException();
        }
    }

W zakładce Retries zobaczymy wtedy wpis informujący nas, że zadanie nie zostało wykonywane i Hangfire próbuje je wykonywać powtórnie:
retries

Oczywiście po pewnym czasie, gdy próby nie przyniosą efektu, zadanie zostanie na stałe przeniesione do “Failed” (gdzie ręcznie możemy wznowić wykonywanie).

Przechodząc do szczegółów zadania, zobaczymy informacje o wyjątku:

error_details

Ponadto HangFire integruje się z popularnymi loggerami (np. nLog, log4net) i jakiekolwiek logi wykonane przez Hangfire będą widoczne w naszych logach. Nic nie trzeba konfigurować – Hangfire automatycznie wykryje, aktualnie wykorzystywanego w systemie loggera.

4 thoughts on “Czasochłonne operacje w ASP.NET – Hangfire”

  1. Też od jakiegoś czasu używam hangfire. Świetne i proste narzędzie. I właśnie ta prostota czasem jest lekkim minusem, ponieważ np:
    – brak obsługi tłumaczeń na inne niż angielski
    – brak jakiegokolwiek wpływu na wygląd dashboard
    – brak powiadomień (np email) o niepowodzeniach (trzeba często ślezzić dashboard)

    Te punkty sprawiają że często wykorzystanie tego narzędzia może być odrzucone, a szkoda bo jest super.

    I na koniec uwaga. Jeśli ktoś korzysta z IoC oraz hostuje hangfire server w innej aplikacji niż dashboard to dla dashboard także trzeba skonfigurować IoC mimo że tylko wyświetla dane z bazy. W przeciwnym wypadku nie wyświetli nam się nazwa i informacja o wykonywanej akcji ( can not find the target method )

  2. @Pawel:
    Co do alertow, mozna to rozwiazac na poziomie logger’a i wysylac po prostu maile w przypadku bledow (np. poprzez hangfire).

  3. Używam tego narzędzia od kilku miesięcy produkcyjnie, jest super i lepszego nie znalazłem. Ciekawostka jest taka, że jeśli masz aplikację w load balancingu czyli mamy kilka instancji na tej samej bazie danych (zakładam storage np. na sql) i jest na nich HangFire serwer to aplikacje będą się prześcigać w tym która pierwsza zrobi zlecone zadanie do kolejki. 🙂 np. instancja A wrzuci task do kolejki to może go podjąć instancja B, ba wydaje mi się, że nawet różne od siebie aplikacje mogą wymieniać się zadaniami o ile są na tym samym serwerze. W dashbord w ostatniej zakładce widać wtedy więcej serwerów – trzeba tylko zadbać żeby każda instancja miała indywidualny identyfikator hangfire serwera (swego czasu to był problem i instancje się blokowały).

    Ale jest jeden minus, który trzeba samemu doimplementować. To że hangfire będzie działał zawsze i nie pominie zadania zależy tylko od IIS (o ile stawiamy na IIS) i tutaj nie jest to do końca wyjaśnione jak zrobić to poprawnie, bo trzeba wykonać odpowiednie ustawienia na IIS oraz zaimplementować IHttpModule z uruchomieniem Hangfire serwera. Dokumentacja akurat w tym zakresie nie jest oczywista. Super byłoby o tym napisać jak to zrobić dobrze. 🙂

Leave a Reply

Your email address will not be published.