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
Related
I have added scheduler in Startup of service like:
services.AddQuartz(q =>
{
q.SchedulerId = "S1";
q.SchedulerName = "S1";
q.UseMicrosoftDependencyInjectionJobFactory();
q.UsePersistentStore(s =>
{
s.UseProperties = true;
s.UsePostgres("ConnectionString");
s.UseJsonSerializer();
});
})
Now I am tring to use this created Scheduler via DI like:
public SchedulerStartup(ISchedulerFactory schedulerFactory)
{
this.schedulerFactory = schedulerFactory;
}
public async Task StartAsync(CancellationToken cancellationToken)
{
Scheduler = await schedulerFactory.GetScheduler("S1", cancellationToken);
await Scheduler.Start(cancellationToken);
}
But somehow Scheduler is null. I wont able to access created Scheduler in startup configuration (S1).
Link: https://www.quartz-scheduler.net/documentation/quartz-3.x/packages/microsoft-di-integration.html#di-aware-job-factories
Here I have missed services.AddQuartzHostedService() to start scheduler using Hosting Service. Additional startup class not required.
This should be like this:
services.AddQuartz(q =>
{
q.SchedulerName = "S1";
q.UseMicrosoftDependencyInjectionJobFactory();
q.UsePersistentStore(s =>
{
s.UseProperties = true;
s.UsePostgres(DbConnectionString);
s.UseJsonSerializer();
});
});
services.AddQuartzHostedService(options =>
{
options.WaitForJobsToComplete = true;
});
Later created instance of this Scheduler can be used as (S1):
public MyRuntimeScheduler(ISchedulerFactory schedulerFactory)
{
Scheduler = schedulerFactory.GetScheduler("S1").GetAwaiter().GetResult();
}
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.
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.
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);
I have created a generic job schedule manager class. I am using Quartz.net 3.0.7 in .net core app.
public class ScheduleManagerManager<TJob> : IScheduleManager where TJob : IJob
{
private readonly string _cronExpression;
private readonly string _jobIdentity;
private readonly string _triggerIdentity;
private IScheduler _scheduler;
private IJobDetail _jobDetail;
private ITrigger _trigger;
public ScheduleManagerManager(string cronExpression)
{
_cronExpression = cronExpression;
_jobIdentity = Guid.NewGuid().ToString();
_triggerIdentity = Guid.NewGuid().ToString();
}
public async Task Run()
{
try
{
_scheduler = await StdSchedulerFactory.GetDefaultScheduler();
await _scheduler.Start();
_jobDetail = JobBuilder.Create<TJob>()
.WithIdentity(_jobIdentity)
.Build();
_trigger = TriggerBuilder.Create()
.WithIdentity(_triggerIdentity)
.StartNow()
.WithCronSchedule(_cronExpression)
.Build();
await _scheduler.ScheduleJob(_jobDetail, _trigger);
}
catch (SchedulerException se)
{
await Console.Out.WriteLineAsync(se.Message);
}
}
public void ShutDown()
{
_scheduler.Shutdown();
}
}
So I amusing this in applications like following:
var manager1 = new ScheduleManagerManager<MailSender>();
var manager2 = new ScheduleManagerManager<SmsSender>();
I want to add an event handler to get job data after executed the job.
var manager1 = new ScheduleManagerManager<MailSender>();
manager1.JobExecuted += OnJobExecuted();
But there is no event in Quartz.net jobs or triggers. How can I do it?
But there is no event in Quartz.net jobs or triggers. Of course they are, they are called Listener.
Just implement IJobListener:
public class MyJobListener : IJobListener
{
public string Name () => "MyListener";
public Task JobToBeExecuted(IJobExecutionContext context){ }
public Task JobExecutionVetoed(IJobExecutionContext context){ }
public Task JobWasExecuted(IJobExecutionContext context, JobExecutionException jobException)
{
// Do after Job stuff....
}
}
And add it to your scheduler in your ScheduleManagerManager:
var myJobListener = new MyJobListener();
_scheduler.ListenerManager.AddJobListener(myJobListener, GroupMatcher<JobKey>.AnyGroup());