Quartz Scheduler seems to be a very robust and mature job scheduler originally created in java. I’ve only just found out about it and find it useful enough to make a note here about it for future use. You can find lots of tutorials and samples and users online for it. It is ported to .Net with Quartz.net. Adding a quartz scheduler to your project is as easy as a Nuget. Simple scheduling can happen right out of the box. It can handle complicated cases, and just about all of the scheduling cases that I've seen. JobDetails, Jobs, and Triggers are all classes you can mix and match up in different combinations. It also handles persistence if you want to hook up to an ado store (several Sql and NoSql avail), and logging is built in with another Nuget- Common.Logging.
If you want to inject services into your IJobs, though, you will need to create your own IJobFactory and start the scheduler that way
Dim schFactory As ISchedulerFactory = _container.Resolve(Of ISchedulerFactory)()
_scheduler = schFactory.GetScheduler()
_scheduler.JobFactory = New ScheduleJobFactory(_container)
Your factory implements NewJob (and with Quartz 2.2, ReturnJob (do nothing unless you have dispose requirements)) and here is where you craete the job with your Container of choice (Autofac here) so the container can inject what it needs...
public IJob NewJob(TriggerFiredBundle bundle, IScheduler scheduler)
{
var jobtype = bundle.JobDetail.JobType;
try
{
var schjobtype = typeof(MyJob<>).MakeGenericType(jobtype);
var schjob = (IJob)Activator.CreateInstance(schjobtype, _container);
return schjob;
}
catch (Exception e)
{
using (var l = _container.BeginLifetimeScope())
{
var logger = _container.Resolve<ILogger>();
logger.Error(e);
}
}
return new NoOpJob();
}
... Your JobFactory gets the container from it's constructor
public class ScheduleJobFactory : ISchedulerFactory
{
ILifetimeScope _container;
public ScheduleJobFactory(ILifetimeScope container)
{
_container = container;
}
}
I couldn't have figured this part out: Your jobfactory points to a jobtype class of T that does the container resolve of T (!) clever pattern.
public class MyJob<T> : IJob where T : IJob
{
ILifetimeScope _container;
public MyJob(ILifetimeScope container)
{
_container = container;
}
public void Execute(IJobExecutionContext context)
{
using (var lscope = _container.BeginLifetimeScope())
{
var someJob = lscope.Resolve<T>();
someJob.Execute(context);
}
}
Your Jobs still inherit from IJob and don't reference the generic job... just register them like normal, and specify these specific classes, in your quartz.jobs.xml
...
Diagnostics- if you have trouble getting your job xml files to work (or are trying to use deprecated 1.0 version xml with upgraded 2.0 quartz) and want to see the logging, you can add Common.Logging config to your app.config. The section def:
<sectionGroup name="common">
<section name="logging" type="Common.Logging.ConfigurationSectionHandler, Common.Logging" />
</sectionGroup>
The config-
<common>
<logging>
<factoryAdapter type="Common.Logging.Simple.ConsoleOutLoggerFactoryAdapter, Common.Logging">
<arg key="level" value="DEBUG" />
<arg key="showLogName" value="true" />
<arg key="showDataTime" value="true" />
<arg key="dateTimeFormat" value="yyyy/MM/dd HH:mm:ss:fff" />
</factoryAdapter>
</logging>
</common>
I've seen lots of online users hook up their common.logging to log4net to monitor the scheduler process, and there is a FactoryAdapter for log4net if you want to go that route.
While we are in config, note that the quartz looks for config in 3 or 4 places. First in a java style file quartz.config, then in app.config, then in some other places. If you want to remove the quartz.config and use just .net style, this is a sample app.config section def-
<section name="quartz" type="System.Configuration.NameValueSectionHandler, System, Version=1.0.5000.0,Culture=neutral, PublicKeyToken=b77a5c561934e089" />
and config-
<quartz>
<add key="quartz.scheduler.instanceName" value="_scheduler" /> <!-- whatfor-->
<!-- Configure Thread Pool -->
<add key="quartz.threadPool.type" value="Quartz.Simpl.SimpleThreadPool, Quartz" />
<add key="quartz.threadPool.threadCount" value="10" />
<add key="quartz.threadPool.threadPriority" value="Normal" />
<!-- Configure Job Store --><!--
<add key="quartz.jobStore.type" value="Quartz.Simpl.RAMJobStore, Quartz" />
<add key="quartz.plugin.xml.type" value="Quartz.Plugin.Xml.JobInitializationPlugin, Quartz" />-->
<add key="quartz.plugin.xml.fileNames" value="~/config/ScheduledJobs.config" />
<!--<add key="quartz.plugin.xml.scanInterval" value="10" />-->
<add key="quartz.plugin.xml.type" value="Quartz.Plugin.Xml.XMLSchedulingDataProcessorPlugin, Quartz" />
</quartz>
That uses the defaults. The XmlSchedulingDataProcessorPlugin I think is the included jobs.xml reader and required if you use xml, as the default it something else.