How do I force a quartz.net job to restart interva

2020-04-15 11:15发布

问题:

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).

回答1:

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.



回答2:

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.



回答3:

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()



回答4:

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);