Quartz.Net Trigger Listeners, Part 4 of Quartz.Net Listeners in Detail

This is the fourth post in the Quartz.Net Listener Tutorial series. It’s also the fourth part of the introduction to listeners overview series. You can find Part 1 here, Part 2 here and Part 3 here. Today we’ll be looking at trigger listeners and how to implement one.

As we mentioned in Part 1, trigger listeners get notified of trigger level events. To implement a trigger listener, you need to implement the ITriggerListener interface. Here’s what that interface looks like:

1
2
3
4
5
6
7
8
public interface ITriggerListener
{
    string Name { get; }
    void TriggerFired(ITrigger trigger, IJobExecutionContext context);
    bool VetoJobExecution(ITrigger trigger, IJobExecutionContext context);
    void TriggerMisfired(ITrigger trigger);
    void TriggerComplete(ITrigger trigger, IJobExecutionContext context, SchedulerInstruction triggerInstructionCode);
}

You’ll notice that trigger listeners get notified when the trigger is fired (line 4), when the trigger misfires (line 6) and when the trigger is complete (line 7). There is one other method that also gets called on the trigger listener that needs special attention. When the VetoJobExecution (line 5) method is called, the trigger listener can tell the scheduler to ignore the trigger. Essentially it vetoes the firing of the trigger.

Now let’s build a trigger listener that implements all of these methods. Download the sample code from the Quartz.Net ebook if you want to follow along. Here’s the code for the trigger listener we’ll be going over:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
using Quartz;
using System;
using System.Reflection;

namespace Examples
{
    class TriggerListenerExample:ITriggerListener
    {
        public void TriggerFired(ITrigger trigger, IJobExecutionContext context)
        {
            Console.WriteLine("The scheduler called {0} for trigger {1}", MethodBase.GetCurrentMethod().Name, trigger.Key);
        }

        public bool VetoJobExecution(ITrigger trigger, IJobExecutionContext context)
        {
            Console.WriteLine("The scheduler called {0} for trigger {1}", MethodBase.GetCurrentMethod().Name,trigger.Key);
            return false;
        }

        public void TriggerComplete(ITrigger trigger, IJobExecutionContext context, SchedulerInstruction triggerInstructionCode)
        {
            Console.WriteLine("The scheduler called {0} for trigger {1}", MethodBase.GetCurrentMethod().Name, trigger.Key);
        }

        public void TriggerMisfired(ITrigger trigger)
        {
            Console.WriteLine("The scheduler called {0} for trigger {1}", MethodBase.GetCurrentMethod().Name, trigger.Key);
        }

        public string Name
        {
            get { return "TriggerListenerExample"; }
        }
    }
}

This is a very simple listener, as it only logs any calls made to it, but it’s quite useful because it provides insight into which methods are called when.

First, take a look at the TriggerFired method (line 9), since it is the very first method that gets called on the trigger listener. After the TriggerFired method gets called, the VetoJobExecution (line 14) gets called. You’ll notice that our implementation returns true always. If this method returns false, then the JobExecutionVetoed method will get called on the job listener (if there are any) and the job will not get executed.

Next, let’s look at the TriggerComplete method (line 20). This method gets called when trigger has completed, which means that it gets called after the trigger has fired and after the associated job has finished executing.

The TriggerMisfired method (line 25) will get called if the trigger ever misfires, so it’s a little out of place, because it’s not really related to the job processing lifecycle.

Finally, let’s look at the Name property (line 30). Not much excitement here, as it just returns the name of the listener.

Now that we’ve reviewed the code for the listener, let’s take a look at the listener in action. Here’s the output generated by running the scheduler with the configured examples.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
The scheduler called SchedulerStarting
The scheduler called JobAdded for job directoryScanJobExample.directoryScanJobExample
The scheduler called JobScheduled for job directoryScanJobExample.directoryScanJobExample, caused by trigger directoryScanJobExampleSimpleTriggerGroup.directoryScanJobExampleSimpleTrigger
The scheduler called JobAdded for job directoryScanJobExample.addDirectoryScanListener
The scheduler called JobScheduled for job directoryScanJobExample.addDirectoryScanListener, caused by trigger directoryScanJobExampleSimpleTriggerGroup.addDirectoryScanListenerSimpleTrigger
The scheduler called JobUnscheduled
The scheduler called JobScheduled for job directoryScanJobExample.directoryScanJobExample, caused by trigger directoryScanJobExampleSimpleTriggerGroup.directoryScanJobExampleSimpleTrigger
The scheduler called JobUnscheduled
The scheduler called JobScheduled for job directoryScanJobExample.addDirectoryScanListener, caused by trigger directoryScanJobExampleSimpleTriggerGroup.addDirectoryScanListenerSimpleTrigger
The scheduler called SchedulerStarted
IsStarted=True
SchedulerInstanceId=NON_CLUSTERED
SchedulerName=ServerScheduler
The scheduler is running. Press any key to stop
The scheduler called TriggerFired for trigger directoryScanJobExampleSimpleTriggerGroup.directoryScanJobExampleSimpleTrigger
The scheduler called TriggerFired for trigger directoryScanJobExampleSimpleTriggerGroup.addDirectoryScanListenerSimpleTrigger
The scheduler called VetoJobExecution for trigger directoryScanJobExampleSimpleTriggerGroup.directoryScanJobExampleSimpleTrigger
The scheduler called VetoJobExecution for trigger directoryScanJobExampleSimpleTriggerGroup.addDirectoryScanListenerSimpleTrigger
The scheduler called JobToBeExecuted for job directoryScanJobExample.directoryScanJobExample
The scheduler called JobToBeExecuted for job directoryScanJobExample.addDirectoryScanListener
Job addDirectoryScanListener in group directoryScanJobExample is about to be executed
Job directoryScanJobExample in group directoryScanJobExample is about to be executed
The scheduler called JobWasExecuted for job directoryScanJobExample.addDirectoryScanListener
The scheduler called JobWasExecuted for job directoryScanJobExample.directoryScanJobExample
Job addDirectoryScanListener in group directoryScanJobExample was executed in 29ms
Job directoryScanJobExample in group directoryScanJobExample was executed in 33ms
The scheduler called TriggerComplete for trigger directoryScanJobExampleSimpleTriggerGroup.directoryScanJobExampleSimpleTrigger
The scheduler called TriggerComplete for trigger directoryScanJobExampleSimpleTriggerGroup.addDirectoryScanListenerSimpleTrigger
The scheduler called TriggerFinalized for trigger directoryScanJobExampleSimpleTriggerGroup.addDirectoryScanListenerSimpleTrigger, belonging to job directoryScanJobExample.addDirectoryScanListener
The scheduler called JobDeleted for job directoryScanJobExample.addDirectoryScanListener
The scheduler called TriggerFired for trigger directoryScanJobExampleSimpleTriggerGroup.directoryScanJobExampleSimpleTrigger
The scheduler called VetoJobExecution for trigger directoryScanJobExampleSimpleTriggerGroup.directoryScanJobExampleSimpleTrigger
The scheduler called JobToBeExecuted for job directoryScanJobExample.directoryScanJobExample
Job directoryScanJobExample in group directoryScanJobExample is about to be executed
The scheduler called JobWasExecuted for job directoryScanJobExample.directoryScanJobExample
Job directoryScanJobExample in group directoryScanJobExample was executed in 5ms
The scheduler called TriggerComplete for trigger directoryScanJobExampleSimpleTriggerGroup.directoryScanJobExampleSimpleTrigger
The scheduler called TriggerFired for trigger directoryScanJobExampleSimpleTriggerGroup.directoryScanJobExampleSimpleTrigger
The scheduler called VetoJobExecution for trigger directoryScanJobExampleSimpleTriggerGroup.directoryScanJobExampleSimpleTrigger
The scheduler called JobToBeExecuted for job directoryScanJobExample.directoryScanJobExample
Job directoryScanJobExample in group directoryScanJobExample is about to be executed
The scheduler called JobWasExecuted for job directoryScanJobExample.directoryScanJobExample
Job directoryScanJobExample in group directoryScanJobExample was executed in 5ms
The scheduler called TriggerComplete for trigger directoryScanJobExampleSimpleTriggerGroup.directoryScanJobExampleSimpleTrigger
The scheduler called TriggerFinalized for trigger directoryScanJobExampleSimpleTriggerGroup.directoryScanJobExampleSimpleTrigger, belonging to job directoryScanJobExample.directoryScanJobExample
The scheduler called JobDeleted for job directoryScanJobExample.directoryScanJobExample
 Shutting down scheduler
The scheduler called SchedulerInStandbyMode
The scheduler called SchedulerShuttingdown
The scheduler called SchedulerShutdown
IsShutdown=True
The scheduler has been shutdown.

This listing includes all of the output from the listeners we’ve covered so far, so it gives us an example of how it all comes together. The first few lines (1-14) are output by the scheduler listener as the scheduler gets started and the jobs get scheduled. Once the scheduler has started, we can see that there are 2 TriggerFired method calls (lines 15 and 16). One runs the directory scan job and the other one basically “installs” the directory scan listener. Next, the scheduler calls the VetoJobExecution method on both trigger listeners (lines 17 and 18). You’ll notice that we only created one listener but it’s getting called for both triggers. That’s because our listener was added with a matcher that matches all of the triggers. Take a look at the PluginExample file if you’re not sure about what’s going on.

Since our trigger listener returns false when the VetoJobExecution method is called, the scheduler then executes the jobs attached to the triggers. After the jobs have finished running, the TriggerComplete method is called for both triggers (lines 27 and 28). This means that the triggers are done for now. One of the triggers is not supposed to run again, so the scheduler calls the TriggerFinalized method on the scheduler listener (line 29). This trigger won’t fire again. Because the job that is attached to that trigger is not set to persist, then the scheduler will delete the job from the schedule as well (line 30).

We still have one trigger that needs to keep firing, so you’ll notice that the cycle will repeat for this trigger (lines 31 through 44). The TriggerFired method is called, then the VetoJobExecution, then the TriggerComplete method is called. Finally, the trigger’s repeat count is zero and then the scheduler will call the TriggerFinalized method on the scheduler listener (not the trigger listener!) and delete the job (lines 45 and 46).

I hope seeing this all in one place makes it come together for you. We’ll stop here for now and in our next post we’ll go over the Quart.Net built-in listeners.