Scheduled Tasks
Last updated
Last updated
The package is what powers scheduled tasks and it can be available to any CFML application by using any of our standalone libraries and frameworks:
YOU DON'T NEED COLDBOX TO RUN ANY SCHEDULED TASKS OR ANY FEATURES OF THE ASYNC PACKAGE. YOU CAN USE ANY OF THE STANDALONE LIBRARIES ABOVE.
However, if you use ColdBox, you get enhanced features and new functionality. For example, the are an enhanced implementation of the core scheduled tasks we will be reviewing in this document.
The async package offers you the ability to schedule tasks and workloads via the that you can register in the async manager. We also provide you with a lovely Scheduler
class that can keep track of all the tasks you would like to be executing in a ScheduledExecutor
. In essence, you have two options when scheduling tasks:
Scheduler Approach: Create a scheduler and register tasks in it
Scheduled Executor Approach: Create a ScheduledExecutor
and send task objects into it
With our scheduled tasks you can run either one-off tasks or periodically tasks.
To create a new scheduler you can call the Async Managers' newScheduler( name )
method. This will create a new coldbox.system.async.tasks.Scheduler
object with the specified name you pass. It will also create a ScheduledExecutor
for you with the default threads count inside the scheduler.. It will be then your responsibility to persist that scheduler so you can use it throughout your application process.
Once you get an instance to that scheduler you can begin to register tasks on it. Once all tasks have been registered you can use the startup()
method to startup the tasks and the shutdown()
method to shutdown all tasks and the linked executor.
The name of the ScheduledExecutor
will be {schedulerName}-scheduler
The following methods are used to impact the operation of all scheduled tasks managed by the scheduler:
By default, all tasks run under the system default timezone which usually is UTC. However, if you would like to change to a different execution timezone, then you can use the setTimeZone()
method and pass in a valid timezone string:
Remember that some timezones utilize daylight savings time. When daylight saving time changes occur, your scheduled task may run twice or even not run at all. For this reason, we recommend avoiding timezone scheduling when possible.
By default the scheduler will register a scheduled
executor with a default of 20 threads for you with a name of {schedulerName}-scheduler.
If you want to add in your own executor as per your configurations, then just call the setExecutor()
method.
Every scheduler has the following properties available to you in the variables
scope
Every scheduler has several utility methods:
Ok, now that we have seen all the capabilities of the scheduler, let's dive deep into scheduling tasks with the task( name )
method.
Once you call on this method, the scheduler will create a ColdBoxScheduledTask
object for you, configure it, wire it, register it and return it to you.
You register the callable event via the call()
method on the task object. You can register a closure/lambda or a invokable CFC. If you register an object, then we will call on the object's run()
method by default, but you can change it using the method
argument and call any public/remote method.
There are many many frequency methods in scheduled tasks that will enable the tasks in specific intervals. Every time you see that an argument receives a timeUnit
the available options are:
days
hours
minutes
seconds
milliseconds (default)
microseconds
nanoseconds
Ok, let's go over the frequency methods:
All time
arguments are defaulted to midnight (00:00)
By default all tasks that have interval rates/periods that will execute on that interval schedule. However, what happens if a task takes longer to execute than the period? Well, by default the task will execute even if the previous one has not executed. If you want to prevent this behavior, then you can use the withNoOverlaps()
method and ColdBox will register the tasks with a fixed delay. Meaning the intervals do not start counting until the last task has finished executing.
Spaced delays are a feature of the Scheduled Executors. There is even a spacedDelay( delay, timeUnit )
method in the Task object.
Every task can also have an initial delay of first execution by using the delay()
method.
The delay
is numeric and the timeUnit
can be:
days
hours
minutes
seconds
milliseconds (default)
microseconds
nanoseconds
Please note that the delay
pushes the execution of the task into the future only for the first execution.
A part from registering tasks that have specific intervals/frequencies you can also register tasks that can be executed ONCE ONLY. These are great for warming up caches, registering yourself with control planes, setting up initial data collections and so much more.
Basically, you don't register a frequency just the callable event. Usually, you can also combine them with a delay of execution, if you need them to fire off after certain amount of time has passed.
We already saw that a scheduler has life-cycle methods, but a task can also have several useful life-cycle methods:
By default, all tasks will ask the scheduler for the timezone to run in. However, you can override it on a task-by-task basis using the setTimezone( timezone )
method:
Remember that some timezones utilize daylight savings time. When daylight saving time changes occur, your scheduled task may run twice or even not run at all. For this reason, we recommend avoiding timezone scheduling when possible.
There are many ways to constrain the execution of a task. However, you can register a when()
closure that will be executed at runtime and boolean evaluated. If true
, then the task can run, else it is disabled.
Every task is runnable from registration according to the frequency you set. However, you can manually disable a task using the disable()
method:
Once you are ready to enable the task, you can use the enable()
method:
All tasks keep track of themselves and have lovely metrics. You can use the getStats()
method to get a a snapshot structure
of the stats in time. Here is what you get in the stats structure:
We have created some useful methods that you can use when working with asynchronous tasks:
Let's investigate now a second approach to task scheduling. We have seen the Scheduler
approach which is a self-contained object that can track multiple tasks for you and give you enhanced and fluent approaches to scheduling. However, there are times, where you just want to use a ScheduledExecutor
to send tasks into for either one-time executions, or also on specific frequencies and skip the Scheduler.
Like with anything in life, there are pros and cons. The Scheduler approach will track all the scheduled future results of each task so you can see their progress, metrics and even cancel them. With this approach, it is more of a set off and forget approach.
Let's get down to business. The first step is to talk to the AsyncManager and register a scheduled executor. You can do this using two methods:
newExecutor( name, type, threads )
- Pass by type
newScheduledExecutor( name, threads )
- Shorthand
Once you register the executor the Async Manager will track it's persistence and then you can request it's usage anywhere in your app via the getExecutor( name )
method or inject it using the executors
injection DSL.
Now that we have a scheduler, we can use the newTask()
method to get a ScheduledTask
, configure it, and send it for execution.
We talk to the executor via the newTask()
method to get a new ScheduledTask
object
We call the start()
method manually, whenever we want to send the task into scheduling
We get a ScheduledFuture
result object so we can track the results of the schedule.
You can very easily create working queues in this approach by being able to send one-off tasks into the executors and forget about them. Let's say we have an app that needs to do some image processing afte ran image has been uploaded. We don't want to hold up (block) the calling thread with it, we upload, send the task for processing and return back their identifier for the operation.
Remember you can set how many threads you want in a executor. It doesn't even have to be a scheduled executor, but could be a cached one which can expand and contract according to work loads.
You can find all valid time zone Id's here:
You can find how to work with executors in our section.
You can find the API Docs for this object here:
You can find all valid time zone Id's here:
As you can see, now we are in mode, and all the docs on it apply. Several things are different in this approach: