3

I have referred the following question but didn't help me to solve the issue.

In Quartz.NET is there a way to set a property that will only allow one instance of a Job to run?

https://github.com/quartznet/quartznet/issues/469

For CronTrigger, Used the following in the scheduler cs.WithMisfireHandlingInstructionDoNothing().

Applied the following attribute in the HelloJob
DisallowConcurrentExecution.

What happened with the code?
In the Execute method, I have set the breaking point. Based on my code the execute method will execute in 10 seconds each.

After hitting the first breaking point, I have waited for another 31 seconds. Then I have removed the breaking point and executed the code, based on my expectation, that should be execute only one time for another attempt.

But the execute method executed 3 times (3*10 seconds) within another 10 seconds.

How to solve this?

Scheduler code.

ISchedulerFactory schedFact = new StdSchedulerFactory();
IScheduler sched = schedFact.GetScheduler();
sched.Start();

// define the job and tie it to our HelloJob class
IJobDetail job = JobBuilder.Create<HelloJob>()
    .WithIdentity("myJob", "group1")
    .Build();

// Trigger the job to run now, and then every 40 seconds
ITrigger trigger = trigger = TriggerBuilder.Create()
    .WithIdentity("trigger3", "group1")
    .WithCronSchedule("0/10 * * * * ?",cs=>cs.WithMisfireHandlingInstructionDoNothing())
    .ForJob("myJob", "group1")
    .Build();

TriggerKey key = new TriggerKey("trigger3", "group1");
sched.ScheduleJob(job, trigger);

Job execution code.

[DisallowConcurrentExecution]
public class HelloJob : IJob
{        
     public static int count = 1;
    public void Execute(IJobExecutionContext context)
    {
         Console.WriteLine(count+" HelloJob strted On." + DateTime.Now.ToString());
        if (count == 1)
            Thread.Sleep(TimeSpan.FromSeconds(30));

        Interlocked.Increment(ref count);
    }
}

enter image description here

====================================================================

Solution

No need to go for interlocked or manual management.

Quartz is already designed like only finishes the first schedule, the next one starts.

So we no need to worry about that it will run concurrently.

For example (The people like me :-p), the scheduler scheduled for 10 mins.

But if we copy the following code in the execute method, you can see that, On the first time, it will take 20 minutes to finish. On the second time, it will take 15 minutes to finish.

In between there won't be next schedule starts after 10 mins over.

   var startTime = DateTime.UtcNow;

            if (count == 1)
            {
                while (DateTime.UtcNow - startTime < TimeSpan.FromSeconds(20))
                {
                    // Execute your loop here...
                }
            }
            else if (count > 1)
            {
                while (DateTime.UtcNow - startTime < TimeSpan.FromSeconds(15))
                {
                    // Execute your loop here...
                }
            }
            count++;
Community
  • 1
  • 1
Jeeva J
  • 3,173
  • 10
  • 38
  • 85

3 Answers3

5

What is happening in you case is that during first long 30 seconds execution of the job Quartz schedules three other executions. That is why after long execution so see three short executions. They are not executed at the same time. One after another but with no delay. That is how Quartz was designed.

[DisallowConcurrentExecution]
public class HelloJob : IJob
{
    public static int count = 1;
    public void Execute(IJobExecutionContext context)
    {
        Console.WriteLine("HelloJob strted On." + DateTime.UtcNow.Ticks);
        if (count == 1)
            Thread.Sleep(TimeSpan.FromSeconds(30));

        Interlocked.Increment(ref count);
    }
}

And the output:

Ctrl+C to exit.
HelloJob strted On.636280218500144776
HelloJob strted On.636280218800201691
HelloJob strted On.636280218800211705
HelloJob strted On.636280218800222083
HelloJob strted On.636280218900009629
HelloJob strted On.636280219000000490

See, the timestamp is different and next starts after first is completed.

If you want to avoid such behavior you should either increase delay between jobs or as @BrunoFerreira suggested handle this job's schedule manually.

Andrii Litvinov
  • 12,402
  • 3
  • 52
  • 59
  • No this is written like always thread sleep for 30 seconds. Check my hellojob class which is updated. At first time, it will run 30 seconds, for the subsequent call, it will run for the 10 seconds. after 30 seconds, it will run 3 times consecutively within 10 seconds – Jeeva J Apr 17 '17 at 10:21
  • You can even set the next delays to 2-3 seconds to ensure they don't execute at the same time. – Andrii Litvinov Apr 17 '17 at 10:39
  • @JeevaJsb, does it answer your question? – Andrii Litvinov Apr 17 '17 at 11:31
  • Thank you I will check.. Do we need really Interlocked.Increment(ref count); with count.. What is interlocked? Is it designed with quartz? – Jeeva J Apr 17 '17 at 14:26
  • Interlocked is a base class that can atomically increment a value it case multiple threads want to do it. It is not needed in this case. It's just to be extra sure and have correct count in case of concurrency. – Andrii Litvinov Apr 17 '17 at 14:29
  • I have tried. But no luck @Andrii.. I have uploaded the result image with my question. – Jeeva J Apr 17 '17 at 14:35
  • @JeevaJsb, maybe you don't completely understant my answer. Job it not triggered concurrently. Only one job runs at a time. Try set `Thread.Sleep(TimeSpan.FromSeconds(2));` after increment of counter and check the output. – Andrii Litvinov Apr 17 '17 at 14:46
  • 1
    Thanks for concern and help. hurrah We no need to go for interlocked. The next schedule starts only finishes the previous job schedule. – Jeeva J Apr 17 '17 at 15:21
2

I've faced the same problem a few days ago. Turn out thant I had to unschedule de job and it's trigger. I'm using this method to do so when my application ends.

private void StopAllJobs()
{
     // construct a scheduler factory
     ISchedulerFactory schedFact = new StdSchedulerFactory();
     // get a scheduler, start the schedular before triggers or anything else
     sched = schedFact.GetScheduler();
     var executingJobs = sched.GetCurrentlyExecutingJobs();
     foreach (var job in executingJobs)
     {
         sched.Interrupt(job.JobDetail.Key);
         sched.UnscheduleJob(job.Trigger.Key);
         sched.DeleteJob(job.JobDetail.Key);
     }
     sched.Clear();
}

Hope this helps you.

Bruno Ferreira
  • 466
  • 7
  • 17
  • I don't understand your flow. when to unschedule all my jobs and why? If i Unschedule when again the schedule will start? – Jeeva J Apr 17 '17 at 09:57
  • No, you have to unschedule when your application ends. In my case, I use jobs in a windows service, so when the service stops I have to unschedule de jobs. If I dont do this, the next time the service stars, I have twice the instances of a job runnig. – Bruno Ferreira Apr 17 '17 at 10:13
  • Why not use only `scheduler.Shutdown(false/true);` in **OnStop** Windows Service ? – Kiquenet May 31 '20 at 08:52
0

Just use something like this:

        private static readonly SemaphoreSlim Lock = new SemaphoreSlim(1, 1);

        if (await Lock.WaitAsync(new TimeSpan(0, 0, 30)))
        {
           ...
        }

We have a few jobs and this works fine.

MeTitus
  • 3,390
  • 2
  • 25
  • 49