Task-scheduler
A Clojure library designed for asynchronous scheduling of tasks
Require the code namespace:
(require '[jansuran03.task-scheduler.core :as scheduler])
=> nil
You can schedule a task to be executed after a certain millisecond timeout, while not blocking the current thread during
the preparation time.
wait-for-tasks
allows you to wait for all scheduled tasks to finish running.
As you can see, each task also has their own ID (which will be important later):
(let [scheduler (scheduler/create-scheduler)]
(scheduler/schedule scheduler :task-1 #(println "Hello after 1 second") 1000)
(scheduler/wait-for-tasks scheduler))
Hello after 1 second
=> true
You can also schedule events to happen in a loop after a certain interval. Calling stop
will immediately terminate the scheduler
and prevent rescheduling of tasks, while letting the tasks, that already started executing, finish:
(let [scheduler (scheduler/create-scheduler)]
(scheduler/schedule-interval scheduler :task-1 #(println "Hello after 1 second") 1000)
(Thread/sleep 2500)
(scheduler/stop scheduler))
Hello after 1 second
Hello after 1 second
=> true
wait-for-tasks
will prevent scheduled tasks from being put back into the queue again as well, but it will also wait for all tasks
in the current queue to be executed naturally and to finish running (including those that were rescheduled and therefore re-queued).
(let [scheduler (scheduler/create-scheduler)]
(scheduler/schedule-interval scheduler :task-1 #(println "Hello after 1 second") 1000)
(scheduler/wait-for-tasks scheduler))
Hello after 1 second
=> true
Calling scheduler/stop-and-wait
is a combination of stop
and wait-for-tasks
, canceling tasks in the queue and
waiting to finish for the ones that already started executing:
=> true
(let [scheduler (scheduler/create-scheduler)]
(scheduler/schedule scheduler :task-1 #(println "Hello after 1 second") 1000)
(scheduler/schedule scheduler :task-1 #(do (Thread/sleep 2000)
(println "Hello after 2.5 seconds")) 500)
(Thread/sleep 700)
(scheduler/wait-for-tasks scheduler))
Hello after 2.5 seconds
=> true
Just to make it clear, stop
, wait-for-tasks
and stop-and-wait
will all terminate the main scheduler loop which
is also responsible for picking up messages from the message channel, and so you cannot schedule any tasks after
calling these functions or call stop
after wait-for-tasks
etc.
You can have guarantees about not scheduling multiple tasks with the same ID - these functions will also check,
whether a task with that ID is scheduled, and discard it eventually. The functions without "new"
schedule
the task no matter what, even allowing to replace an interval task with a one-time task and the other way.
(let [scheduler (scheduler/create-scheduler)]
(scheduler/schedule-new scheduler :task-1 #(println "Hello 1") 1000)
(scheduler/schedule-new scheduler :task-1 #(println "Hello 2") 500)
(scheduler/schedule-new-interval scheduler :task-2 #(println "Hello 3") 300)
(scheduler/schedule-new-interval scheduler :task-2 #(println "Hello 4") 300)
(Thread/sleep 400)
(scheduler/wait-for-tasks scheduler))
Hello 3
Hello 3
Hello 1
=> true
The most important use of the IDs is canceling a task by its ID.
(let [scheduler (scheduler/create-scheduler)]
(scheduler/schedule-new-interval scheduler :task-2 #(println "Hello 3") 300)
(scheduler/schedule-new-interval scheduler :task-2 #(println "Hello 4") 300)
(Thread/sleep 400)
(scheduler/cancel-schedule scheduler :task-2)
(scheduler/wait-for-tasks scheduler))
Hello 3
=> true
All operations, which might or might not have succeeded, return a promise with the result:
(let [scheduler (scheduler/create-scheduler)]
[@(scheduler/cancel-schedule scheduler :foo) ; false
@(scheduler/schedule-new scheduler :foo #(println "Foo") 100) ; true
@(scheduler/schedule-new scheduler :foo #(println "Foo") 100) ; false
@(scheduler/schedule-new-interval scheduler :bar #(println "Bar") 100) ; true
@(scheduler/schedule-new-interval scheduler :bar #(println "Bar") 100) ; false
@(scheduler/cancel-schedule scheduler :foo) ; true
@(scheduler/cancel-schedule scheduler :bar)]) ; true
=> [false true false true false true true]
Additionally, you can define your own task handler which should execute the task asynchronously and return immediately (blocking for a long time would block the scheduler main loop).
; default:
(scheduler/create-scheduler {:exec-fn #(clojure.core.async/go (%))})
; other examples:
(scheduler/create-scheduler {:exec-fn #(future (%))})
(let [executor (SomeExecutor/create)]
(scheduler/create-scheduler {:exec-fn #(.execute executor %)}))
Copyright © 2024 Jan Šuráň
This program and the accompanying materials are made available under the terms of the Eclipse Public License 2.0 which is available at http://www.eclipse.org/legal/epl-2.0.
This Source Code may also be made available under the following Secondary Licenses when the conditions for such availability set forth in the Eclipse Public License, v. 2.0 are satisfied: GNU General Public License as published by the Free Software Foundation, either version 2 of the License, or (at your option) any later version, with the GNU Classpath Exception which is available at https://www.gnu.org/software/classpath/license.html.