Czasami potrzeba nam prostego narzędzia, które będzie wykonywało jakieś zadania w określonych ramach czasowych. Można użyć prostego Timera z .NET Framework, ale ma on dość ograniczone możliwości. Na przykład, stan zadań nie może być zapisany w bazie danych. Dla bardzo zaawansowanych rozwiązań, zwykle mamy inną architekturę, na przykład opartą na kolejkach. W takich sytuacjach, zwykle poszczególne technologie posiadają swoje mechanizmy, tak jak nServiceBus o który już wiele razy pisałem.
Dzisiaj jednak chciałbym przedstawić Quartz.NET – lekka biblioteka, która nada się do do prostych przypadków, dla których jednak czysty, standardowy timer ma zbyt małe możliwości.
Standardowo instalujemy pakiet z NuGet:
API jest bardzo proste. Standardowy szablon wygląda następująco:
try { IScheduler scheduler = StdSchedulerFactory.GetDefaultScheduler(); scheduler.Start(); Thread.Sleep(1000000); scheduler.Shutdown(); } catch (SchedulerException ex) { Console.WriteLine(ex); }
Koniecznie należy wywołać Shutdown na koniec. W przeciwnym wypadku, wciąż w aplikacji istniałyby wątki przeznaczone do wykonywania zadań. Same zadanie definiuje się poprzez implementację interfejsu IJob:
class PrintTextJob : IJob { public void Execute(IJobExecutionContext context) { Console.WriteLine("Test: {0}",DateTime.Now); } }
Następnie należy ją dodać do scheduler’a:
IJobDetail job = JobBuilder.Create<PrintTextJob>().Build(); ITrigger trigger = TriggerBuilder.Create(). StartNow(). WithSimpleSchedule(x => x.WithIntervalInSeconds(10).RepeatForever()).Build(); scheduler.ScheduleJob(job, trigger);
Definicja Triggera jest tutaj chyba najważniejsza. Szczegóły znajdują się oczywiście w dokumentacji, ale istnieje wiele sposób zdefiniowania kiedy zadanie ma wykonać się. Do dyspozycji mamy nawet cron expression:
TriggerBuilder.Create().WithCronSchedule("0 42 10 * * ?")
Można również zdefiniować zachowanie scheduler’a za pomocą pliku konfiguracyjnego:
quartz.scheduler.instanceName = MyScheduler quartz.threadPool.threadCount = 3 quartz.jobStore.type = Quartz.Simpl.RAMJobStore, Quartz
Jak widać, można określić liczbę wątków oraz typ bazy. Domyślnie jest to in-memory, ale nic nie stoi na przeszkodzie, aby zapisać wyzwalacze czy zadania w innym typie bazy.
Quartz wspiera wykonywanie logów i możemy je zdefiniować za pomocą:
Common.Logging.LogManager.Adapter = new Common.Logging.Simple.ConsoleOutLoggerFactoryAdapter {Level = Common.Logging.LogLevel.Info};
Możemy również śledzić wszelkie wykonywane operacje np. poprzez implementacje interfejsu ISchedulerListener:
public interface ISchedulerListener { void JobScheduled(Trigger trigger); void JobUnscheduled(string triggerName, string triggerGroup); void TriggerFinalized(Trigger trigger); void TriggersPaused(string triggerName, string triggerGroup); void TriggersResumed(string triggerName, string triggerGroup); void JobsPaused(string jobName, string jobGroup); void JobsResumed(string jobName, string jobGroup); void SchedulerError(string msg, SchedulerException cause); void SchedulerShutdown(); }
Następnie zaimplementowany listener należy dodać, a po wszystkim oczywiście usunąć:
scheduler.ListenerManager.AddSchedulerListener(mySchedListener); scheduler.ListenerManager.RemoveSchedulerListener(mySchedListener);
Analogicznie sprawa wygląda z ITriggerListener oraz IJobListener:
public interface ITriggerListener { string Name { get; } void TriggerFired(ITrigger trigger, IJobExecutionContext context); bool VetoJobExecution(ITrigger trigger, IJobExecutionContext context); void TriggerMisfired(ITrigger trigger); void TriggerComplete(ITrigger trigger, IJobExecutionContext context, int triggerInstructionCode); } public interface IJobListener { string Name { get; } void JobToBeExecuted(IJobExecutionContext context); void JobExecutionVetoed(IJobExecutionContext context); void JobWasExecuted(IJobExecutionContext context, JobExecutionException jobException); }
Następnie wystarczy wywołać scheduler.ListenerManager.AddJobListener.
Równie ciekawa alternatywa:
http://hangfire.io/
Jeśli .net 4.5.2 to moze QueueBackgroundWorkItem
http://bit.ly/1kP1Qul
Dobra biblioteka, też z niej korzystam. Przy konfiguracji warto zwrócić uwagę na to by pula aplikacji była zawsze dostępna.
Czy scheduler z powyższego przykładu jest FIFO? Tzn czy kolejne zadanie wykona się dopiero po zakończeniu pierwszego?