commercial .net task scheduler component that supports callbacks or events - c#

I am aware of quartz.net and the codeplex task scheduler managed wrapper project. They have a rather decent learning curve and R&D phase to get it to work as per our specs. Also packaging them & configuring with our runtime installer is another issue. So we have decided to go with a commercial .NET task scheduler.
Our requirements are:
Must be supported on Win xp and Win 7 (x86 + x64)
Must provide a callback or event when trigger is fired.
Sample psuedo code:
Trigger mytrigger = new Trigger(Daily, "8:00am", myCallbackDelegate);
mytrigger.Start();
Every day at 8:00 method pointed to by myCallbackDelegate will be called.
The scheduler can run as service or everytime the app that references it is started.
.NET component that can be dropped on the form and configured preferred.
Please provide your recommendations. I have googled and cannot find anything that will do this basic functionality. thanks

I have used Quartz.Net in a WCF Service and it has worked really good, it has lots of flexibility due to the Cron Triggers, basically you can work out most of the scenarios of scheduling, when you schedule a trigger, you need to specify a type of a class that implements the IJob Interface. In my case the Execute methods calls a singleton class/method to do the job it needs to perform. You can configure the Triggers to be stored on RAM (volatile) or a Database, i think you can specify a custom storage but i haven't go that way.
The only problem that i had with Quartz.NET is described in this question, I also posted the solution that i worked out, if you have more specific questions please let me know.
This is some of the configuration basics of Quartz.NET mosthly followed from the Tutorial
For instantiating the Scheduler you do something like this:
ISchedulerFactory schedulerFactory = new StdSchedulerFactory();
_scheduler = schedulerFactory.GetScheduler();
_scheduler.Start()
For scheduling a job you will do something like this
JobDetail jobDetail = new JobDetail("UNIQUE NAME", null, typeof(NotepadJob));
SimpleTrigger triggerToReturn = new SimpleTrigger();
triggerToReturn.StartTimeUtc = DateTime.Now.ToUniversalTime();
_scheduler.ScheduleJob(jobDetail,trigger);
and the Job will be something like this
internal class NotepadJob : IJob
{
//Open Notepad
}
If wokring with SQL you can configure the settings as followed on the Config file:
<configSections>
<section name="quartz" type="System.Configuration.NameValueSectionHandler, System, Version=1.0.5000.0,Culture=neutral, PublicKeyToken=b77a5c561934e089" />
<\configSections>
<quartz>
<add key="quartz.scheduler.instanceName" value="DefaultQuartzJobScheduler" />
<add key="quartz.scheduler.instanceId" value="AUTO" />
<add key="quartz.jobstore.clustered" value="true" />
<add key="quartz.jobstore.clusterCheckinInterval" value="15000" />
<add key="quartz.threadPool.type" value="Quartz.Simpl.SimpleThreadPool, Quartz" />
<add key="quartz.jobStore.useProperties" value="false" />
<add key="quartz.jobStore.type" value="Quartz.Impl.AdoJobStore.JobStoreTX, Quartz" />
<add key="quartz.jobStore.driverDelegateType" value="Quartz.Impl.AdoJobStore.SqlServerDelegate, Quartz" />
<add key="quartz.jobStore.tablePrefix" value="QRTZ_" />
<add key="quartz.jobStore.lockHandler.type" value="Quartz.Impl.AdoJobStore.UpdateLockRowSemaphore, Quartz" />
<add key="quartz.jobStore.misfireThreshold" value="60000" />
<add key="quartz.jobStore.dataSource" value="default" />
<add key="quartz.dataSource.default.connectionString" value="[CONNECTION STRING]" />
<add key="quartz.dataSource.default.provider" value="SqlServer-20" />
<add key="quartz.threadPool.threadCount" value="10" />
</quartz>
-Regards

Related

Quartz.net scheduler does not take latest changes to Jobs class file

In our asp.net web forms application we have daily jobs that need to send some emails to users.
Recently we started to use Quartz.net to allow jobs run non-sequentially. I am using the ADO Job store to schedule the jobs and create the triggers. While creating the job data I am also giving the .dll(full path) and the class name that has to be executed for that job. This is a console application that is run only once.
IScheduler scheduler = StdSchedulerFactory.GetDefaultScheduler();
JobDataMap jobData = new JobDataMap();
jobData.Add("JobID", jobDataRow["Id"].ToString());
jobData.Add("AssemblyName", assemblyName);
jobData.Add("ClassName", className);
jobData.Add("AssemblyPath", assemblyPath);
var jobDetail = JobBuilder.Create()
.StoreDurably()
.WithIdentity(name)
.SetJobData(jobData)
.Build(); ;
ITrigger trigger = TriggerBuilder.Create()
.WithIdentity(triggerName)
.WithCronSchedule(cronexpr)
.StartNow()
.Build();
scheduler.ScheduleJob(jobDetail, trigger, true);
Then we have a windows service that starts the scheduler. This windows service is stopped and restarted after every build.
StdSchedulerFactory schedulerFactory = new
StdSchedulerFactory(schedulerProperties);
scheduler = schedulerFactory.GetScheduler();
scheduler.Start();
Quartz properties declared :-
<add key="quartz.threadPool.threadCount" value="10" />
<add key="quartz.threadPool.threadPriority" value="2" />
<add key="quartz.jobStore.misfireThreshold" value="60000" />
<add key="quartz.scheduler.instanceId" value="Auto" />
<add key="quartz.threadPool.type" value="Quartz.Simpl.SimpleThreadPool, Quartz" />
<add key="quartz.jobStore.type" value="Quartz.Impl.AdoJobStore.JobStoreTX, Quartz" />
<add key="quartz.jobStore.dataSource" value="default" />
<add key="quartz.jobStore.tablePrefix" value="QRTZ_" />
<add key="quartz.jobStore.clustered" value="false" />
<add key="quartz.jobStore.lockHandler.type" value="Quartz.Impl.AdoJobStore.UpdateLockRowSemaphore, Quartz" />
<add key="quartz.dataSource.default.provider" value="SqlServer-20" />
<add key="quartz.jobStore.driverDelegateType" value="Quartz.Impl.AdoJobStore.SqlServerDelegate, Quartz" />
<add key="quartz.jobStore.useProperties" value="true" />
<add key="quartz.jobStore.misfireThreshold" value="60000" />
<add key="quartz.plugin.triggHistory.type" value="Quartz.Plugin.History.LoggingJobHistoryPlugin" />
<add key="quartz.scheduler.exporter.type" value="Quartz.Simpl.RemotingSchedulerExporter, Quartz" />
<add key="quartz.scheduler.exporter.port" value="555" />
<add key="quartz.scheduler.exporter.bindName" value="QuartzScheduler" />
<add key="quartz.scheduler.exporter.channelType" value="tcp" />
<add key="quartz.scheduler.exporter.channelName" value="httpQuartz" />
Now the issue is - When any change is done to the class file like changes to the existing email template, the job still sends the emails using the old email template when run from Quartz scheduler.
If we run the job manually then the emails are sent correctly with the new changes.
Do we need to recreate the Quartz job data map(QRTZ_JOB_DETAILS table) every time when there is a change to the class files?
Please help.. I am stuck here and couldn't find any related post on this.
Thanks you in advance!!
If the Quartz process is already running and the DLL contents (jobs) have been loaded, they won't be automatically loaded again. This is how .NET CLR works. Once the process has loaded the type from givem DLL, it won't be reloaded by Quartz.
I don't know how you deployment process works, but I would see two options at least.
Restart the process after you deploy new DLL version, sounds like basic deployment
Make your jobs load templates from file system so you can change them in different cadence than what you use for the main process - but good release process would also update the binaries to the same level so this might not be the recommended approach

How to run/trigger old schdeuled jobs on windows service start in AdoJobStore

I have a system where i am scheduling some job and trigger to run at some specific time in a windows service . As my windows service can be restarted for number o0f reason I wanted to trigger the already schedule jobs at the time they were schedule so i am using AdoJobStore as it persist the job/trigger information. I were thinking at the time of start of the scheduler it will load the non completed job and fire it. But it can't
I have configured a Adojobstore with following setting
<add key="quartz.scheduler.instanceName" value="DBScheduler" />
<add key="quartz.threadPool.type" value="Quartz.Simpl.SimpleThreadPool, Quartz" />
<add key="quartz.threadPool.threadCount" value="100" />
<add key="quartz.threadPool.threadPriority" value="2" />
<add key="quartz.scheduler.dbFailureRetryInterval" value="6000000000" />
<add key="quartz.jobStore.driverDelegateType" value="Quartz.Impl.AdoJobStore.SqlServerDelegate, Quartz" />
<add key="quartz.jobStore.tablePrefix" value="QRTZ_" />
<add key="quartz.jobStore.misfireThreshold" value="600000" />
<add key="quartz.jobStore.type" value="Quartz.Impl.AdoJobStore.JobStoreTX, Quartz" />
<add key="quartz.jobStore.dataSource" value="myDS" />
<add key="quartz.dataSource.myDS.connectionString" value="Server=localhost;Port=3306;database=quartznet;Uid=root;pwd=root;allow user variables=true;CharSet=utf8;" />
<add key="quartz.dataSource.myDS.provider" value="MySql" />
<add key="quartz.serializer.type" value="binary" />
And Code for schdeuling start looks like following at my windows start class
StdSchedulerFactory factory = new StdSchedulerFactory();
MyQuartzScheduler = await factory.GetScheduler();
await MyQuartzScheduler.Start();
What i thought a future job which i have schedules and who's entries are saved in quartznet db will trigger at there time after starting the schedule but it didn't.
Is there any other settings or code i required to add?
Looks Like Problem was due to one configuration settings
It appears following configuration setting was the culprit
<add key="quartz.jobStore.driverDelegateType" value="Quartz.Impl.AdoJobStore.SqlServerDelegate, Quartz" />
Changed it to
<add key="quartz.jobStore.driverDelegateType" value="Quartz.Impl.AdoJobStore.StdAdoDelegate, Quartz" />
And it started working.
Can some quartz.net experience person confirm the mistake

Start quartz.net jobs more frequently

We're using Quartz.net and need the jobs to fire more often.
Job is set to fire in 3 seconds but it usually takes 15-30 seconds before it's run.
I've also tried (without result)
< add key="quartz.jobStore.clusterCheckinInterval" value="1000" />
Thanks for any help PS. We're using 2.1.2.400 if it matters
Our configs
<quartz>
<add key="quartz.scheduler.instanceName" value="ServerScheduler" />
<add key="quartz.scheduler.instanceId" value="AUTO" />
<add key="quartz.threadPool.type" value="Quartz.Simpl.SimpleThreadPool, Quartz" />
<add key="quartz.threadPool.threadCount" value="10" />
<add key="quartz.threadPool.threadPriority" value="2" />
<add key="quartz.jobStore.misfireThreshold" value="60000" />
<add key="quartz.jobStore.clusterCheckinInterval" value="1000" />
<add key="quartz.jobStore.type" value="Quartz.Impl.AdoJobStore.JobStoreTX, Quartz" />
<add key="quartz.jobStore.useProperties" value="false" />
<add key="quartz.jobStore.dataSource" value="default" />
<add key="quartz.jobStore.tablePrefix" value="QRTZ_" />
<add key="quartz.jobStore.clustered" value="true" />
<add key="quartz.jobStore.lockHandler.type" value="Quartz.Impl.AdoJobStore.UpdateLockRowSemaphore, Quartz" />
<add key="quartz.dataSource.default.connectionString" value="Data Source=xyz..." />
<add key="quartz.dataSource.default.connectionStringName" value="RecDB" />
<add key="quartz.dataSource.default.provider" value="SqlServer-20" />
</quartz>
Code
var jobSilentDetail = CreateFutureJobDetail(new JobKey(JobName(shoppingListNotification.Id)),
typeof(JobShoppinglistNotification),
string.Format("Job bla bla"));
jobSilentDetail.JobDataMap["ShoppingListNotification"] = shoppingListNotification;
var startJobAt = DateTime.Now.AddSeconds(3);
SaveFutureJob(jobSilentDetail, startJobAt);
protected void SaveImmediateJob(IJobDetail jobDetail)
{
Scheduler.ScheduleJob(jobDetail, CreateImmediateTriggerFor(jobDetail));
}
protected void SaveFutureJob(IJobDetail jobDetail, DateTime startTime)
{
Scheduler.ScheduleJob(jobDetail, CreateFutureTriggerFor(jobDetail, startTime.ToUniversalTime()));
}
protected ITrigger CreateImmediateTriggerFor(IJobDetail jobDetail)
{
return CreateOneRunTriggerFor(jobDetail, QuartzJobType.Immediate, DateTime.Now);
}
protected ITrigger CreateFutureTriggerFor(IJobDetail jobdetail, DateTime startTime)
{
return CreateOneRunTriggerFor(jobdetail, QuartzJobType.Future, startTime);
}
private static ITrigger CreateOneRunTriggerFor(IJobDetail jobDetail, QuartzJobType quartzJobType, DateTime startTime)
{
var trigger = TriggerBuilder
.Create()
.WithIdentity(jobDetail.Key.Name, quartzJobType.ToString())
.WithSimpleSchedule()
.StartAt(startTime.ToUniversalTime())
.Build();
return trigger;
}
I'd strongly suggest trying out the latest version as you have brought up in the comments. There was a bug with AdoJobStore job handling taking some time in some cases that was fixed in version 2.2.3. Please also not that when upgrading to >= 2.2 series a (minor) database schema upgrade is needed.
You can see the release history in here.
It's always advisable to check release notes for later releases if you are experiencing issues.
Well we never got Quartz.net to fire fast enough. Took about 15 seconds instead of the needed 3. So we totally changed approach.
Instead of MANY future jobs that should be run 3 seconds after they where created. We created ONE recurring job that fires every 2 seconds and write logic (with a different db table) to figure out what items have passed the 3 second waiting time.
Hope this helps someone else out there that get stuck with the same problem.

Is the config file name "quartz.config" fixed (means hard-code, cannot be changed)?

I know that Quartz.Net has several ways to loads it configuration upon startup: (from http://jvilalta.blogspot.com/2011/03/how-does-quartznet-configuration-work.html )
The hosting application’s configuration file
A file specified in an environment variable
The quartz.config file
The embedded configuration file
Notice the quartz.config file.
My question:
Is the config file name "quartz.config" fixed (means hard-code,
cannot be changed)?
If no, how can I change it? e.g. I want to read from
FinancialQuartz.config instead of quartz.config.
If no option to change the name "quartz.config". How can I specify
when to read from FinancialQuartz.config or
CalculationQuartz.config? (I have no real scenario for this
question, just wonder)
Regards,
I'm confused.
In DotNet. You can point the app.config or web.config file to your file of choice, like below.
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<configSections>
<section name="quartz" type="System.Configuration.NameValueSectionHandler, System, Version=1.0.5000.0,Culture=neutral, PublicKeyToken=b77a5c561934e089" />
</sectionGroup>
</configSections>
<quartz configSource="MyQuartzSettings.config" />
And "MyQuartzSettings.config" looks something like this (one example of many)
<quartz>
<add key="quartz.plugin.jobInitializer.type" value="Quartz.Plugin.Xml.XMLSchedulingDataProcessorPlugin" />
<add key="quartz.scheduler.instanceName" value="DefaultQuartzScheduler" />
<add key="quartz.threadPool.type" value="Quartz.Simpl.SimpleThreadPool, Quartz" />
<add key="quartz.threadPool.threadCount" value="10" />
<add key="quartz.threadPool.threadPriority" value="2" />
<add key="quartz.jobStore.misfireThreshold" value="60000" />
<add key="quartz.jobStore.type" value="Quartz.Simpl.RAMJobStore, Quartz" />
<add key="quartz.plugin.jobInitializer.fileNames" value="Quartz_Jobs_001.xml" />
<add key="quartz.plugin.jobInitializer.failOnFileNotFound" value="true" />
<add key="quartz.plugin.jobInitializer.scanInterval" value="120" />
</quartz>
Is that what you're talking about?
In addition to the answer given by #granadaCoder, you can also set the quartz.config environment variable to the name of the file you want to load. Note that the configSource attribute is not quartz.net specific but a feature of the .Net framework: SectionInformation.ConfigSource Property
I think it's fixed now. But there's a workaround for it.
Read the configuration from other ways into a System.Collections.Specialized.NameValueCollection.
var builder = new ConfigurationBuilder()
.SetBasePath(Directory.GetCurrentDirectory())
.AddIniFile("someotherfile.ini", false, true);
var config = builder.Build();
var quartzProperties = new System.Collections.Specialized.NameValueCollection();
foreach (var p in config.GetSection("Quartz").GetChildren().AsEnumerable())
{
quartzProperties.Add(p.Key, p.Value);
}
IScheduler scheduler = new StdSchedulerFactory(quartzProperties).GetScheduler();
scheduler.Start();

Castle ActiveRecord - SessionScopeWebModule

Now and again im getting the following error
"Seems that the framework isn't configured properly. (isWeb != true and SessionScopeWebModule is in use) Check the documentation for further information"
and my web app crashes
my web.config is so..
<activerecord isweb="true">
<config>
<add key="connection.driver_class" value="NHibernate.Driver.SqlClientDriver" />
<add key="dialect" value="NHibernate.Dialect.MsSql2005Dialect" />
<add key="connection.provider" value="NHibernate.Connection.DriverConnectionProvider" />
<add key="connection.connection_string_name" value="main" />
<add key="proxyfactory.factory_class" value="NHibernate.ByteCode.Castle.ProxyFactoryFactory, NHibernate.ByteCode.Castle"/>
</config>
</activerecord>
any ideas, on what I need to change\add?
thank you
isWeb is case sensitive.
<activerecord isWeb="true">
From the source:
XmlAttribute isWebAtt = section.Attributes["isWeb"];
They aren't being nice about case :)

Categories