Quartz.net schedule a task to run for 5 minutes - c#

My requeriment is to build a Quartz.net Task that:
Runs at specified time for a variable amount of minutes and then finish?
I have this class:
public class Proccess
{
public static void Start()
{
Console.WriteLine("I'm starting");
}
public static void End()
{
Console.WriteLine("I'm finishing");
}
}
Is there any way to configure a job with Quartz.Net to call Process.Start() wait for X minutes and the call Process.End() ?
Maybe somethings like this?
public class TapWaterJob : IJob
{
public TapWaterJob()
{
// quartz requires a public empty constructor so that the
// scheduler can instantiate the class whenever it needs.
}
public void Execute(IJobExecutionContext context)
{
Proccess.Start();
System.Threading.Thread.Sleep(10000);
Process.End();
}
}

If you just want to run something to completion, it seems like just letting object stop on it's own is the right way to do it (i.e. call stop for itself).
If you want Quartz to invoke the stop, you could do something like this:
var jobMap = new JobDataMap();
jobMap.Put("process", process);
var startJob = JobBuilder.Create<StartJob>()
.WithIdentity("startJob")
.SetJobData(jobMap).Build();
ITrigger startTrigger = TriggerBuilder.Create()
.WithIdentity("startTrigger")
.StartNow()
.Build();
var stopJob = JobBuilder.Create<StopJob>()
.WithIdentity("stopJob")
.SetJobData(jobMap).Build();
ITrigger stopTrigger = TriggerBuilder.Create()
.WithIdentity("stopTrigger")
.StartAt(DateTime.Now.AddMinutes(5))
.Build();
var sched = StdSchedulerFactory.GetDefaultScheduler();
sched.Start();
sched.ScheduleJob(startJob, startTrigger);
sched.ScheduleJob(stopJob, stopTrigger);
Replace the 5 parameter to StartAt with a variable with the number of minutes you want to wait.

Related

QUARTZ.NET not executing process

I'm using newest 3.0.6 version of Quartz.net
I want o schedule a process to run each day (currently in example it's every 40seconds but I will change that)
I have method where I call it to execute, however it does not seem to start any process which it should as I initiated it in `Job' class. I'm not sure why it's not trigerring?
public static void Run()
{
var isRunning = false;
IServiceCollection services = new ServiceCollection();
Startup startup = new Startup();
var serviceProvider = startup.ConfigureServices(services);
IWindsorContainer _container = serviceProvider.GetService<IWindsorContainer>();
while (true)
{
if (!isRunning)
{
isRunning = true;
var configuration = _container.Resolve<IConfigurationRoot>();
var _process = _container.Resolve<Process.Process>();
int secondsForSleep = Convert.ToUInt16(configuration[Enums.SleepTime]);
{
try
{
execute();
}
catch (Exception ex)
{
}
finally
{
isRunning = false;
Thread.Sleep(secondsForSleep);
_container.Release(_process);
}
}
}
}
}
public static async void execute()
{
// construct a scheduler factory
NameValueCollection props = new NameValueCollection
{
{ "quartz.serializer.type", "binary" }
};
StdSchedulerFactory factory = new StdSchedulerFactory(props);
// get a scheduler
IScheduler sched = await factory.GetScheduler();
await sched.Start();
IJobDetail job = JobBuilder.Create<Job>()
.WithIdentity("myJob", "group1")
.Build();
ITrigger trigger = TriggerBuilder.Create()
.WithIdentity("myTrigger", "group1")
.StartNow()
.WithSimpleSchedule(x => x
.WithIntervalInSeconds(1)
.RepeatForever())
.Build();
await sched.ScheduleJob(job, trigger);
}
}
public class Job : IJob
{
public async Task Execute(IJobExecutionContext context)
{
JobKey key = context.JobDetail.Key;
IServiceCollection services = new ServiceCollection();
Startup startup = new Startup();
var serviceProvider = startup.ConfigureServices(services);
IWindsorContainer _container = serviceProvider.GetService<IWindsorContainer>();
var configuration = _container.Resolve<IConfigurationRoot>();
var _process = _container.Resolve<Process.Process>();
Task t = new Task(() =>
{
_process.MainProcess();
});
t.Start();
await t;
}
}
Do I incorrectly set IJobDetail object or something? does anyone had a similar problem? Seems like i did eveerything according to documentation but I still can't get it working
This is not directly the answer to your question but may help you if the problem is related to below.
Look, you have defined the execute method as async void. As a result, you cannot call it via await execute() and so
your try-catch block will not handle any exceptions raised in the execute method
finally block runs directly after calling execute() method (not waiting for method finishing)
In other words, change your code to async Task execute() and await execute() and test it again.
For more theory I suggest you read this SO: async/await - when to return a Task vs void? and msdn: Async/Await - Best Practices

Job execution to call existing controller action method

I have two action method DayStart() and DayEnd() and I have to call DayStart()
at 8:05 am (Mon-Fri) and DayEnd() at 8:04 am (Mon-Fri) and for this the best option so far in Quartz.net but I don't know how do I call these two action method in jobExecution method.
Is there any way to achieve this?
public ActionResult StartDay()
{
int userid = Auth.UserID;
daysServices.StartDay(userid);
return RedirectToAction("Index", "Home", new { isdayClose = false });
}
public ActionResult CloseDay()
{
int userid = Auth.UserID;
int dayId = daysServices.getActiveDay();
int shiftId = daysServices.getActiveShift();
daysServices.CLoseDay(dayId, userid, shiftId);
return RedirectToAction("TokenDayAmount", "Home", new { isdayClose = true });
}
public async Task Execute(IJobExecutionContext context)
{
await Console.Out.WriteLineAsync("HelloJob is executing.");
//I hope something like this? DayStart();
}
You need three steps for this. First create a job
public class SomeJob : IJob
{
public void Execute(IJobExecutionContext context)
{
DayStart(); //this is your start method
}
}
Second you need a scheduler when your job will execute. You can use Cron Trigger to schedule your job like
public class SomeJobScheduler
{
public static void Start()
{
IScheduler scheduler = StdSchedulerFactory.GetDefaultScheduler();
scheduler.Start();
IJobDetail job = JobBuilder.Create<SomeJob>().Build();
ITrigger trigger = TriggerBuilder.Create()
.WithCronSchedule("0 05 8 ? * MON-FRI *") //This expression to schedule your job Mon-Fri 8.05 AM
.Build();
scheduler.ScheduleJob(job, trigger);
}
}
You can find more about Cron Ttrigger Here
Third call your Scheduler in Global,asax.cs.
void Application_Start(object sender, EventArgs e)
{
SomeJobScheduler.Start();
}

Handling failures of chained jobs using Quartz.NET

I was able to schedule 3 chained jobs using Quartz.NET. This strategy is working fine:
var j1 = new TestJob1();
var j2 = new TestJob2();
var j3 = new TestJob3();
var jd1 = j1.Build();
var jd2 = j2.Build();
var jd3 = j3.Build();
var chain = new JobChainingJobListener("jobchain");
chain.AddJobChainLink(jd1.Key, jd2.Key);
chain.AddJobChainLink(jd2.Key, jd3.Key);
Scheduler.ListenerManager.AddJobListener(chain, GroupMatcher<JobKey>.AnyGroup());
Scheduler.ScheduleJob(jd1, j1.JobTrigger());
Scheduler.AddJob(jd2, true);
Scheduler.AddJob(jd3, true);
Scheduler.Start();
The code for each job is as follows:
public class TestJob1 : BaseJob, IJob
{
public override ITrigger JobTrigger()
{
return TriggerBuilder.Create()
.WithSimpleSchedule(
ssb =>
ssb.WithInterval(new TimeSpan(0, 0, 0, 10)).RepeatForever().WithMisfireHandlingInstructionFireNow())
.Build();
}
public void Execute(IJobExecutionContext context)
{
Debug.WriteLine($"Running Job 1 at {DateTime.Now.ToString("O")}");
}
}
public class TestJob2 : BaseJob, IJob
{
public override ITrigger JobTrigger()
{
throw new System.NotImplementedException();
}
public void Execute(IJobExecutionContext context)
{
Debug.WriteLine($"Running Job 2 at {DateTime.Now.ToString("O")}");
throw new Exception("forced error");
}
}
public class TestJob3 : BaseJob, IJob
{
public override ITrigger JobTrigger()
{
throw new System.NotImplementedException();
}
public void Execute(IJobExecutionContext context)
{
Debug.WriteLine($"Running Job 3 at {DateTime.Now.ToString("O")}");
}
}
If you see, the TestJob2 is throwing an exception when it runs. Even on this situation, the TestJob3 is fired. My business requirement is that TestJob3 shouldn't be fired if TestJob2 fails. Notice that actually I don't need to implement the trigger for job2 and job3 because I'm adding those jobs without a trigger to the scheduler.
How would this be done?
Thanks in advance,
Mário
Subclass JobChainingJobListener, and use JobChainingJobListenerFailOnError in place of JobChainingJobListener:
/// <summary>
/// JobChainingJobListener that doesn't run subsequent jobs when one fails.
/// </summary>
public class JobChainingJobListenerFailOnError : JobChainingJobListener
{
public JobChainingJobListenerFailOnError(String name) : base(name) { }
public override void JobWasExecuted(IJobExecutionContext context, JobExecutionException jobException)
{
//Only call the base method if jobException is null. Otherwise, an
//error has occurred & we don't want to continue chaining
if (jobException == null)
base.JobWasExecuted(context, jobException);
}
}
While the JobChain is new to me I would still employ base clr api calls and take advantage of the Task Parallel library (tpl) by chaining tasks (https://msdn.microsoft.com/en-us/library/ee372288(v=vs.110).aspx).
This particular code chains four different tasks together and one cannot fire without the previous finishing. All i want Quartz to do is schedule and call my job. I apologize if i veered away from the quartz api but i just wanted provide a way i handled multiple tasks.
In my job i enter the job Execute and execute calls Process()
private async Task<Boolean> Process()
{
Task<bool> t1 = Task<bool>.Factory.StartNew(() =>
{
return processThis();
});
Task<bool> t2 = t1.ContinueWith((ProcessMore) =>
{
return processMoreStuff();
});
Task<bool> t3 = t2.ContinueWith((ProcessEvenMore) =>
{
return processEvenMoreStuff();
});
Task<bool> t4 = t3.ContinueWith((ProcessStillSomeMore) =>
{
return processStillMoreStuff();
});
var result = await t4;
try
{
Task.WaitAll(t1, t2, t3, t4);
}
catch (Exception ex)
{
System.Diagnostics.Trace.WriteLine(ex.Message);
}
return result;
}
I've had to chain jobs in the past but I did not use JobChainingJobListener.
What I do is add and schedule the (n+1)th job when the (n)th job is done. This is helpful for example when the next job to execute depends on the result of the current job, as it does in your case.
To continue to use JobChainingJobListener I think you could get the TestJob3 and set a flag in its data map, when TestJob2 ends successfully. TestJob3 will still be executed when there is an exception in TestJob2 but you just have to check your flag to see if it needs to carry on with its execution.

callback to class from quartz job

I have different classes as descendants of a base class (Worker).
Each of these classes has it's own quartz job and trigger and a
callback.
public class Worker1 : Worker, ICallback
{
IScheduler scheduler;
public Worker1(IScheduler scheduler)
: base("Worker1")
{
this.scheduler = scheduler;
IJobDetail job = JobBuilder.Create<MonitorJob>()
.WithIdentity(name + "Job")
.Build();
ITrigger trigger = TriggerBuilder.Create()
.WithIdentity(name + "Trigger")
.StartNow()
.WithSimpleSchedule(x => x
.WithIntervalInSeconds(1)
.RepeatForever())
.Build();
scheduler.ScheduleJob(job, trigger);
}
public void Callback()
{
Console.WriteLine(name + " callback " + DateTime.Now.ToLongTimeString());
}
}
Now I want on trigger of job1 (from worker1) a callback to worker1.callback.
Same with job2 (from worker2) a callback to worker2.callback.
With autofac I am able to inject a callback into my job - however I can only inject a common callback, not as I like one for each class.
public class MonitorJob : IJob
{
ICallback callback;
public MonitorJob(ICallback callback)
{
this.callback = callback;
}
public void Execute(IJobExecutionContext context)
{
callback.Callback();
}
}
my main class creates the autofac container
container = ConfigureContainer(new ContainerBuilder()).Build();
using (container.BeginLifetimeScope())
{
workers.Add(container.Resolve<Worker1>());
workers.Add(container.Resolve<Worker2>());
}
var factory = container.Resolve<ISchedulerFactory>();
var scheduler = factory.GetScheduler();
scheduler.Start();
internal static ContainerBuilder ConfigureContainer(ContainerBuilder cb)
{
cb.RegisterModule(new QuartzAutofacFactoryModule());
cb.RegisterModule(new QuartzAutofacJobsModule(typeof(QuartzScheduler.MonitorJob).Assembly));
cb.RegisterType<Worker1>().AsSelf();
cb.RegisterType<Worker2>().AsSelf();
return cb;
}
Autofac creates the job and also could inject it with a callback, however I want the right callback from the worker class that "owns" the job.
How would this be possible?
Otherwise I would need to somehow propagate from one common callback to the respective worker class.
Thank you in advance
I succeeded by creating a generic job:
public class MonitorJob<T> : IJob where T:IWorker
{
T worker;
public MonitorJob(T worker)
{
this.worker = worker;
}
public void Execute(IJobExecutionContext context)
{
worker.Callback();
}
}
In my custom worker class I create the monitor job for this worker:
public Worker1(IScheduler scheduler)
{
this.scheduler = scheduler;
IJobDetail job = JobBuilder.Create<MonitorJob<Worker1>>()
.WithIdentity(name + "Job")
.Build();
[...]
}
and so the right callback class will be resolved by:
container.RegisterType<Worker1>().AsSelf().SingleInstance();
container.RegisterType<MonitorJob<Worker1>>().AsSelf();
[...]
workers.Add(scope.Resolve<Worker1>());
so always the right callback is executed for each individual job.
Autofac has no way to know how to bind ICallback and IJob. How would like to bind them ?
The simplest solution is to introduce a dependency between these 2 classes
public class MonitorJob : IJob
{
public MonitorJob(Worker1 worker1)
{
this._worker1 = worker1;
}
private readonly Worker1 _worker1;
public void Execute(IJobExecutionContext context)
{
this._worker1.Callback();
}
}
If it is not possible you can use Keyed service :
public class MonitorJob : IJob
{
public MonitorJob([WithKey("Worker1")]ICallback callback)
{
this._callback = callback;
}
private readonly ICallback _callback;
public void Execute(IJobExecutionContext context)
{
this._callback.Callback();
}
}
and you will have to register it this way cb.RegisterType<Worker1>().Keyed<ICallback>("Worker1")
If it is still not possible to modify your code this way, you can create a new module to add parameters :
public class CallbackModule : Module
{
protected override void AttachToComponentRegistration(IComponentRegistry componentRegistry, IComponentRegistration registration)
{
if (registration.Services.OfType<TypedService>()
.Any(s => s.ServiceType == typeof(IJob)))
{
registration.Preparing += (sender, e) =>
{
// bind ICallback and IJob here
if (registration.Activator.LimitType == typeof(Job1))
{
e.Parameters = e.Parameters
.Concat(new TypedParameter[] { TypedParameter.From<ICallback>(e.Context.Resolve<Worker1>()) });
}
};
}
}
}
And the registration can be made this way :
ContainerBuilder cb = new ContainerBuilder();
cb.RegisterModule(new CallbackModule());
cb.RegisterType<Worker1>().As<ICallback>().AsSelf();
cb.RegisterType<Worker2>().As<ICallback>().AsSelf();
IContainer container = cb.Build();
By the way, the following code contains a mistake:
using (container.BeginLifetimeScope())
{
workers.Add(container.Resolve<Worker1>());
workers.Add(container.Resolve<Worker2>());
}
You create a new ILifetimeScope but don't use it. The following code should be use instead :
using (ILifetimeScope scope = container.BeginLifetimeScope())
{
workers.Add(scope.Resolve<Worker1>());
workers.Add(scope.Resolve<Worker2>());
}
If you want to be notified when the execute method is called, you can implement a JobListener for this. You can create one listener for all jobs or multiple listeners, one per job type perhaps, and then apply the appropriate matcher. Here's a link that might be useful with more details on job listeners and listeners in general: Quartz.Net Job Listeners, Part 3 of Quartz.net Listeners in detail

How do I force a quartz.net job to restart intervall after completion

I have a project where I use TopShelf and TopShelf.Quartz
Following this example I am building my jobs with
s.ScheduleQuartzJob(q =>
q.WithJob(() => JobBuilder.Create<MyJob>().Build())
.AddTrigger(() => TriggerBuilder.Create()
.WithSimpleSchedule(builder => builder
.WithIntervalInSeconds(5)
.RepeatForever())
.Build())
);
which fires my job every five seconds even if the previous is still running. What I really want to achive is to start a job and after the completion wait five seconds and start again. Is this possible or do I have to implement my own logic (for example via a static variable).
A job listener as proposed by #NateKerkhofs will work, like this:
public class RepeatAfterCompletionJobListener : IJobListener
{
private readonly TimeSpan interval;
public RepeatAfterCompletionJobListener(TimeSpan interval)
{
this.interval = interval;
}
public void JobExecutionVetoed(IJobExecutionContext context)
{
}
public void JobToBeExecuted(IJobExecutionContext context)
{
}
public void JobWasExecuted(IJobExecutionContext context, JobExecutionException jobException)
{
string triggerKey = context.JobDetail.Key.Name + ".trigger";
var trigger = TriggerBuilder.Create()
.WithIdentity(triggerKey)
.StartAt(new DateTimeOffset(DateTime.UtcNow.Add(interval)))
.Build();
context.Scheduler.RescheduleJob(new TriggerKey(triggerKey), trigger);
}
public string Name
{
get
{
return "RepeatAfterCompletionJobListener";
}
}
}
Then add the listener to the scheduler:
var jobKey = "myJobKey";
var schedule = new StdSchedulerFactory().GetScheduler();
listener = new
RepeatAfterCompletionJobListener(TimeSpan.FromSeconds(5));
schedule.ListenerManager.AddJobListener
(listener, KeyMatcher<JobKey>.KeyEquals(new JobKey(jobKey)));
var job = JobBuilder.Create(MyJob)
.WithIdentity(jobKey)
.Build();
// Schedule the job to start in 5 seconds to give the service time to initialise
var trigger = TriggerBuilder.Create()
.WithIdentity(CreateTriggerKey(jobKey))
.StartAt(DateTimeOffset.Now.AddSeconds(5))
.Build();
schedule.ScheduleJob(job, trigger);
Unfortunately I don't know how to do this (or if it can be done) with the fluent syntax used by Typshelf.Quartz library, I use this with TopShelf and regular Quartz.Net.
You can use a TriggerListener (http://www.quartz-scheduler.net/documentation/quartz-2.x/tutorial/trigger-and-job-listeners.html) to listen to when the trigger finishes, then reschedule in 5 seconds.
Another option is to schedule the next job as the final action in the Execute of the job itself.
http://www.quartz-scheduler.net/documentation/faq.html has a question somewhere 2/3rds of the way down that explains more about it.
The JobListener solution is a very powerful and flexible way to reschedule your job after completion. Thanks to Nate Kerkhofs and stuartd for the input.
In my case it was sufficient to decorate my Job class with the DisallowConcurrentExecution attribute since I don't have different instances of my job
[DisallowConcurrentExecution]
public class MyJob : IJob
{
}
FYI: Using a JobListerener with TopShelf.Quartz the code could look like this
var jobName = "MyJob";
var jobKey = new JobKey(jobName);
s.ScheduleQuartzJob(q =>
q.WithJob(() => JobBuilder.Create<MyJob>()
.WithIdentity(jobKey).Build())
.AddTrigger(() => TriggerBuilder.Create()
.WithSimpleSchedule(builder => builder
.WithIntervalInSeconds(5)
.Build())
var listener = new RepeatAfterCompletionJobListener(TimeSpan.FromSeconds(5));
var listenerManager = ScheduleJobServiceConfiguratorExtensions
.SchedulerFactory().ListenerManager;
listenerManager.AddJobListener(listener, KeyMatcher<JobKey>.KeyEquals(jobKey));
If you are using TopShelf.Quartz.Ninject (like I do) don't forget to call UseQuartzNinject() prior to calling ScheduleJobServiceConfiguratorExtensions.SchedulerFactory()
The best way I found is to add simple Job Listener.
In my example it reschedules job, just after failure.
Of cause you can add delay in .StartAt(DateTime.UtcNow)
public class QuartzRetryJobListner : IJobListener
{
public string Name => GetType().Name;
public async Task JobExecutionVetoed(IJobExecutionContext context, CancellationToken cancellationToken = default) => await Task.CompletedTask;
public async Task JobToBeExecuted(IJobExecutionContext context, CancellationToken cancellationToken = default) => await Task.CompletedTask;
public async Task JobWasExecuted(
IJobExecutionContext context,
JobExecutionException jobException,
CancellationToken cancellationToken = default)
{
if (jobException == null) return;
// Create and schedule new trigger
ITrigger retryTrigger = TriggerBuilder.Create()
.StartAt(DateTime.UtcNow)
.Build();
await context.Scheduler.ScheduleJob(context.JobDetail, new[] { retryTrigger }, true);
}
}
Also, I think it's useful to add class extension
public static class QuartzExtensions
{
public static void RepeatJobAfterFall(this IScheduler scheduler, IJobDetail job)
{
scheduler.ListenerManager.AddJobListener(
new QuartzRetryJobListner(),
KeyMatcher<JobKey>.KeyEquals(job.Key));
}
}
Just for simplify usage.
_scheduler.ScheduleJob(job, trigger);
//In case of failue repeat job immediately
_scheduler.RepeatJobAfterFall(job);

Categories