Biblioteka Quartz.NET

W tym wpisie opisuje bibliotekę Quartz.NET, dzięki której można zarządzać harmonogramem wykonywania zadań w aplikacji.

Cześć! Dzisiejszy wpis będzie poświęcony bibliotece Quartz.NET, dzięki której w łatwy sposób można zarządzać wykonywaniem zadań w obrębie aplikacji. Biblioteka Quarts.NET to lekka i bardzo intuicyjna biblioteka oraz ma spore możliwości jeśli chodzi o planowanie zadań. Zaczynajmy!

Biblioteka Quartz.NET – co to jest?

Biblioteka Quartz.NET to narzędzie które umożliwia planowanie wykonywania zadań w aplikacji, co oznacza, że można wykonywać pewne fragmenty (komponenty) aplikacji w określonych interwałach czasowych lub – co odróżnia już na starcie Quartz.NET od zwykłych .NET-owy timerów – o określonych porach w wybrane dni tygodnia, miesiąca itp..

Quartz.NET jest bardzo przyjazną biblioteką, posiada kilka interfejsów oraz paradygmatów, które w łatwy sposób można użyć w każdym projekcie. Konfiguracja biblioteki wymaga niewielkiego nakładu pracy, co powoduje, że w zasadzie można zacząć z niej korzystać zaraz po dodaniu paczki nuget. Najważnieszymi elementami składowymi biblioteki Quartz.NET są:

  • Job – to pojedyncze zadanie, jakie ma się wykonać, zadanie definiowane musi zostać przy użyciu interfejsu IJob.
  • Trigger – kontroluje wywoływanie się pojedynczych Jobów. Jego zadaniem jest zdefiniowanie zasad wywołania konkretnego Joba – kiedy ma się odbyć, jak często powtarzać, itp..
  • Scheduler – to serce biblioteki, ponieważ odpowiada za zarządzanie Triggerami i Jobami.

To co warto jeszcze dodać, to fakt, że biblioteka Quartz.NET potrafi zapamiętać harmonogram zadań – to jest przydatne w momencie awarii aplikacji – kiedy dochodzi do ponownego uruchomienia. Wtedy po restarcie Scheduler potrafi odtworzyć swój stan i kontynuować swoje działanie. Oczywiście zapraszam na stronę biblioteki Quartz.NET, gdzie znajdziecie więcej informacji na jej temat, tutaj link. A poniżej prosty przykład wykorzystania Quartz.NET w aplikacji.

Biblioteka Quartz.NET – przykładowe użycie

Na potrzeby tego wpisu utworzymy prostą aplikację, która cyklicznie będzie pobierać informacje o temperaturze z kilku źródeł i wyświetlać je w konsoli. Aby zacząć korzystać z biblioteki Quartz.NET najwygodniej zainstalować ją z paczki Nuget. Instalujemy najnowszą dostępną wersję biblioteki Quartz.NET.

W tym momencie można przejść do implementacji kodu aplikacji. Tak jak wspominałem w tym przykładzie utworzę prostą aplikację konsolową, która wyświetlać będzie temperatury z różnych źródeł.

Zacznijmy od definicji serwisu temperatury. Poniżej jego definicja, jak widać jest to bardzo trywialny przykład, ale nie chodzi o tworzenie skomplikowanej struktury lecz o pokazanie działania biblioteki Quartz.NET.

internal interface ITemperatureService
{
  double GetTemperature();
}

Mając już zdefiniowany interfejs utworzymy jego implementacje. Przykładowo utworzymy serwis do pobierania temperatury jaka jest w kuchni, analogicznie można utworzyć implementacje dla innych źródeł.

internal class KitchenTemperatureService : ITemperatureService
{
	public double GetTemperature()
    {
    	var random = new Random();
        return random.Next(20, 30);
	}
}

Mając zaimplementowaną logikę biznesową powiedzmy, że potrzebujemy pobierać dane o temperaturze z różnych pomieszczeń o różnych porach dnia. W tym celu trzeba zdefiniować zadania (Joby), aby to zrobić wykorzystamy interfejs dostarczony przez Quartz.NET – IJob. Poniżej definicja Joba, którego zadaniem jest pobieranie informacji o temperaturze w kuchni.

internal class KitchenTemperatureJob : IJob
{
	private readonly ITemperatureService _temperatureService;
    
	public KitchenTemperatureJob()
    {
    	_temperatureService = new KitchenTemperatureService();
	}

	public Task Execute(IJobExecutionContext context)
    {
    	return Task.Run(() =>
        {
        	var temperature = _temperatureService.GetTemperature();
            Console.ForegroundColor = ConsoleColor.DarkCyan;
            Console.WriteLine($"Temperatura w kuchni: {temperature} *C");
		});
	}
}

Jak widać wykorzystujemy w tym Jobie wcześniej zdefiniowany serwis do pobierania temperatury. Logika w Jobach może być dużo bardziej skomplikowana np. poprzez wykorzystywanie Dependency Injection. Mając gotową implementację Joba, możemy w startowym punkcie programu – w aplikacji konsolowej będzie to funkcja Main – zdefiniować pozostałe Joby. Analogicznie do Joba pobierającego temperaturę w kuchni, zdefiniowałem dwa inne:

// define jobs
var kitchenJob = JobBuilder.Create<KitchenTemperatureJob>().Build();
var bedroomJob = JobBuilder.Create<BedroomTemperatureJob>().Build();
var outsideJob = JobBuilder.Create<OutsideTemperatureJob>().Build();

Zdefiniowane Joby będą wykonywane przez inny komponent biblioteki Quartz.NET – Trigger. Każdy Job będzie wykonywany przez osobny Trigger. Poniżej konfiguracja Triggerów, które będą uruchamiać wcześniej zdefiniowane Joby:

// define triggers
var kitchenTrigger = TriggerBuilder.Create().WithCronSchedule("0 0 14 * * ?").Build();

var bedroomTrigger = TriggerBuilder.Create()
  .WithSimpleSchedule(x => x.WithIntervalInHours(2).RepeatForever())
  .Build();

var outsideTrigger = TriggerBuilder.Create()
  .WithSimpleSchedule(x => x.WithIntervalInMinutes(5).RepeatForever())
  .Build();

Konfigurowanie Triggerów jest bardzo intuicyjne, dzięki wielu zdefiniowanym w bibliotece metodom do ustawiania czasu wykonywania Jobów. Najciekawsza konfiguracja, która najbardziej zwraca uwagę to konfiguracja pierwszego Triggera.

var kitchenTrigger = TriggerBuilder.Create().WithCronSchedule("0 0 14 * * ?").Build();

Zapis jaki został użyty to tzw. Cron Expressions – pewnie dla osób, które na co dzień pracują na systemach Linux będzie on znany. Dla mniej odważnych podsyłam link – cron expresssions 🙂
Zapis ten oznacza, że Trigger będzie wykonywał przypisanego do niego Joba codziennie o godzinie 14:00.

Po zdefiniowaniu Jobów oraz Triggerów możemy przejść do zdefiniowania głównego komponentu biblioteki Quartz.NET – Schedulera:

// create scheduler
var factory = new StdSchedulerFactory();
var scheduler = await factory.GetScheduler();
await scheduler.Start();

W tym momencie mamy utworzony Scheduler, jak widać wykorzystany jest tutaj wzorzec fabryki. Na nasze potrzeby wystarczy nam wyprodukowanie standardowego Schedulara. Zaraz po utworzeniu uruchamiamy go i przechodzimy do kolejnego ważnego punktu – połączenia wcześniej utworzonych Jobów z Triggerami. Do tego celu wykorzystamy słownik, który będzie przyjmował jako klucz Joba, a wartość podpięte do niego Triggery.

var dictionary = new Dictionary<IJobDetail, IReadOnlyCollection<ITrigger>>();
dictionary.Add(kitchenJob, new HashSet<ITrigger>() {kitchenTrigger});
dictionary.Add(bedroomJob, new HashSet<ITrigger>() { bedroomTrigger });
dictionary.Add(outsideJob, new HashSet<ITrigger>() { outsideTrigger });

Akurat w przykładowej aplikacji każdy Job ma własnego Triggera, jednak może się zdarzyć, że jeden Job będzie wywoływany przez kilka Triggerów – dlatego wymagane jest podanie kolekcji Triggerów. Na koniec dodajemy naszą definicje słownikową do zdefiniowanego Schedulera:

await scheduler.ScheduleJobs(dictionary, true);

To tyle.

Po uruchomieniu aplikacji w oknie konsoli będą się pojawiać informacje o temperaturach w odstępach czasowych zdefiniowanych przy pomocy Triggerów.

Przykładowy rezultat działania aplikacji.

Biblioteka Quartz.NET – podsumowanie

To tyle w tym temacie. Przykład może bardzo trywialny lecz myślę, że pokazujący jakie możliwości daje wykorzystanie biblioteki Quartz.NET. Zamiast komunikatów w konsoli, można emitować eventy do innych komponentów systemu. Możliwości są naprawdę duże. Dziękuję za przeczytanie tego wpisu. Pamiętaj, że zawsze możesz się ze mną skontaktować w razie jakichkolwiek pytań – możesz to zrobić przez formularz kontaktowy. Będę również bardzo wdzięczny jeżeli podzielisz się tym materiałem ze swoimi znajomymi poprzez udostępnienie na LinkedIn lub w innych mediach społecznościowych. Dzięki!

Podziel się swoją opinią
Mateusz Łysień
Mateusz Łysień

Programuje i czasem coś piszę na blogu.

Artykuły: 14