This package can be used for simple tasks that need to run on an interval or at a certain time.
It has both an async and non-async api (They are similar, only difference being in creation of scheduler)
Making tasks
The main procs for creating tasks are
- every When you want a task to run on an interval (First run is when start() is called)
- at When you want the task to run once at a certain date (Runs once)
- wait When you want to wait a certain amount of time and then run the task (Runs once)
Example: cmd: -r:off
import taskman let tasks = newAsyncScheduler() # or newScheduler if you don't want to use async tasks.every(5.minutes) do () {.async.}: echo "This will run every 5 minutes" tasks.at("2077-01-1".parse("yyyy-mm-dd")) do () {.async.}: echo "This will run on the 1st of janurary in 2077" tasks.wait(10.hours) do () {.async.}: echo "This will run after 10 hours" # Block the thread and run tasks when needed # asyncCheck could be used instead e.g. so you can have a web server running also # You could also put it onto another thread waitFor tasks.start()By default the scheduler will end when all tasks are finished (i.e. every at and wait task has completed). It also will run tasks late if they are added while the scheduler is sleeping while waiting for a tasks start time. To change this behaviour you can add a periodicCheck parameter to the start function which will make the scheduler check every periodicCheck seconds for which task it should be waiting on
Example: cmd: -r:off
import taskman let tasks = newAsyncScheduler() proc main() {.async.} = tasks.every(10.minutes) do () {.async.}: echo "10 minutes has passed" asyncCheck tasks.start(100) # The scheduler will check every 100 milliseconds for new tasks # If we didn't have the periodicCheck passed before then this task would only run after # the 10 minutes task before has ran tasks.wait(5.seconds) do () {.async.}: echo "5 seconds has passed" waitFor main()
Deleting Tasks
Tasks can be directly removed using del
Example:
import taskman let tasks = newScheduler() taskA = newTask[TaskHandler](5.seconds, proc () = echo "hello") tasks &= taskA tasks.every(1.days, (proc () = echo "A new dawn appears"), name = "newDay") assert tasks.len == 2 # Tasks can be deleted either with their name or using the original task obj tasks.del taskA tasks.del "newDay" assert tasks.len == 0If inside a task, then removeTask can be used to remove the current task (This is just a helper to raise RemoveTaskException). onlyRun is another helper which calls removeTask after a task has run a certain number of times
Example:
import taskman let tasks = newScheduler() tasks.every(1.milliseconds) do (): if true: echo "I don't want to run anymore" removeTask() # tasks.every(1.milliseconds) do (): if not false: raise (ref RemoveTaskException)() var i = 1 tasks.every(1.nanoseconds) do (): onlyRun(3) # This task will only run three times (Terminates at the end of the 3 run) echo i inc i assert tasks.len == 3 tasks.start() # Tasks will be deleted when ran in this scenario assert tasks.len == 0
Cron
For more advanced intervals you can instead use cron timers
Example:
import taskman let tasks = newScheduler() # Just like * * * * * tasks.every(cron(x, x, x, x, x)) do (): echo "Minute has passed" tasks.every(cron(5, 10, x, x, x)) do (): echo "It is the 5th minute of the 10th hour"See the cron module for more info on the syntax
Types
AsyncTaskHandler = proc (): Future[void] {..}
-
Proc that runs in an async schedulerNote: When using --threads:on the proc must be gcsafeSource Edit
ErrorHandler[T] = proc (s: SchedulerBase[T]; task: TaskBase[T]; exception: ref Exception) {..}
- The error handler will be called when a task raises an exception. Can be used to do things like reschedule a proc to be called earlier or to just ignore errors Source Edit
RemoveTaskException = object of CatchableError
- Throw this within a task to remove it from the scheduler Source Edit
SchedulerBase[T] = ref object tasks*: HeapQueue[TaskBase[T]] running: bool errorHandler*: ErrorHandler[T] when T is AsyncTaskHandler: timer
- Source Edit
TaskBase[T] = ref object handler*: T startTime*: Time name*: string case kind*: TimerKind of Interval: interval*: TimeInterval of Cron: cronFormat*: Cron of OneShot: nil
-
- handler: The proc that handles the task being called
- startTime: The time that the task should be called
- name: The name of the task, useful for debugging with error messages (default is defaultTaskName)
- oneShot: Whether the task only runs once (true) or runs continuiously (false)
TaskHandler = proc () {..}
-
Proc that runs in a normal schedulerNote: When using --threads:on the proc must be gcsafeSource Edit
Consts
defaultTaskName = "task"
- Default name for a task. Will be shown in error messages Source Edit
Procs
proc add[T: HandlerTypes](scheduler: SchedulerBase[T]; task: TaskBase[T]) {. inline.}
- Adds a task to the scheduler. Source Edit
proc at[T: HandlerTypes](scheduler: SchedulerBase[T]; interval: TimeInterval; name: string; handler: T)
- Sugar that allows you to have name and lambda Source Edit
proc at[T: HandlerTypes](scheduler: SchedulerBase[T]; time: DateTime; handler: T; name = defaultTaskName)
-
Runs a task at a certain date/time (only runs once).
Example:
let tasks = newAsyncScheduler() tasks.at("2077-03-06".parse("yyyy-MM-dd")) do () {.async.}: echo "The date is now 2077-03-06"
Source Edit proc del(scheduler: SchedulerBase[HandlerTypes]; task: string)
-
Removes a task from the scheduler (via its name). If there are multiple tasks with the same name then the first task is deleted
Example:
let tasks = newScheduler() proc handler() = echo "Time has passed" tasks.every(5.minutes, handler, name = "time pass") tasks.del "time pass" doAssert tasks.len == 0
Source Edit proc del[T: HandlerTypes](scheduler: SchedulerBase[T]; task: TaskBase[T])
-
Removes a task from the scheduler
Example:
import std/sugar # Create task that echos "hello" every 5 seconds let tasks = newScheduler() proc sayHello() = echo "Hello" let task = newTask[TaskHandler](5.seconds, sayHello, name = "Hello world") tasks &= task # Run scheduler in background, do other stuff # Then the task can be deleted anytime later tasks.del task doAssert tasks.len == 0
Source Edit proc every[T: HandlerTypes](scheduler: SchedulerBase[T]; cron: Cron; handler: T; name = defaultTaskName)
-
Runs a task every time a cron timer is valid
Example:
let tasks = newAsyncScheduler() tasks.every(cron(x, x, x, x, x)) do () {.async.}: echo "A minute has passed"
Source Edit proc every[T: HandlerTypes](scheduler: SchedulerBase[T]; interval: TimeInterval; handler: T; name = defaultTaskName)
-
Runs a task every time the interval occurs.
Example:
let tasks = newAsyncScheduler() tasks.every(5.seconds) do () {.async.}: echo "5 seconds has passed, see you again in 5 seconds" tasks.every(2.hours) do () {.async.}: echo "2 hours has passed, see you again in 2 hours"
Source Edit proc every[T: HandlerTypes](scheduler: SchedulerBase[T]; often: TimeInterval | Cron; name: string; handler: T)
- Sugar that allows you to have name and lambda Source Edit
proc len(scheduler: SchedulerBase): int {.inline.}
- Returns number of tasks in the scheduler Source Edit
proc newAsyncScheduler(errorHandler: ErrorHandler[AsyncTaskHandler] = defaultErrorHandler[ AsyncTaskHandler]): AsyncScheduler {....raises: [], tags: [].}
- Creates an async version of Scheduler. See newScheduler for more details Source Edit
proc newScheduler(errorHandler: ErrorHandler[TaskHandler] = defaultErrorHandler[ TaskHandler]): Scheduler {....raises: [], tags: [].}
-
Creates a sync version of Scheduler
Example:
# By default exceptions are raised let tasks = newScheduler() # But it can be overridden let tasksError = newScheduler() do (s: Scheduler, task: Task, exception: ref Exception): ## Echo exception and then reschedule the task to run in 5 seconds # Scheduler is passed so that the error handler can be its own proc # and used in multiple schedulers and also be gcsafe echo exception.msg task.startTime = getTime() + 5.seconds
Source Edit proc newTask[T: HandlerTypes](cron: Cron; handler: T; name = defaultTaskName): TaskBase[ T]
- Creates a new task which can be added to a scheduler. Source Edit
proc newTask[T: HandlerTypes](interval: TimeInterval; handler: T; name = defaultTaskName): TaskBase[T]
- Creates a new task which can be added to a scheduler. This task will run every interval Source Edit
proc newTask[T: HandlerTypes](time: DateTime; handler: T; name = defaultTaskName): TaskBase[ T]
- Creates a new task which can be added to a scheduler. This task will only run once (will run at time) Source Edit
proc next(task: TaskBase): Time {....raises: [TooFarAheadCron].}
- Returns the next date that a task will run at (If it ran now). If the task is a oneShot then it just returns its start time Source Edit
proc removeTask() {....raises: [RemoveTaskException], tags: [].}
-
Is used in a running task to make it not be called again. (Two tasks are considered equal if they have the same handler proc even if the times are different so this might cause issues)
Example:
let tasks = newScheduler() var i = 0 tasks.every(5.seconds) do (): if i == 5: # Stop the task after the fifth time it has ran and never run it again removeTask() else: # Do important stuff inc i
Source Edit func running(tasks: SchedulerBase): bool {.inline.}
- Returns true if the scheduler is running Source Edit
proc start(scheduler: AsyncScheduler; periodicCheck = 0): owned(Future[void]) {. ...raises: [Exception], tags: [RootEffect, TimeEffect].}
-
Starts running the tasks. Call with asyncCheck to make it run in the background
- periodicCheck: This prevents the scheduler from fulling stopping and specifies how many milliseconds to poll for new tasks. Also use this with non async scheduler to allow you to add new tasks in that might be shorter than current running one
proc start(scheduler: Scheduler; periodicCheck = 0) {. ...raises: [TooFarAheadCron, Exception], tags: [TimeEffect, RootEffect].}
- Source Edit
proc wait[T: HandlerTypes](scheduler: SchedulerBase[T]; interval: TimeInterval; handler: T; name = defaultTaskName)
-
Waits interval amount of time and then runs task (only runs once).
Example: cmd: --threads:off
import std/httpclient let tasks = newAsyncScheduler() let client = newAsyncHttpClient() # I need to send message reminder in a few minutes tasks.wait(5.minutes) do () {.async.}: asyncCheck client.post("http://notificationurl.com", "Your reminder message")
Source Edit proc wait[T: HandlerTypes](scheduler: SchedulerBase[T]; interval: TimeInterval; name: string; handler: T)
- Sugar that allows you to have name and lambda Source Edit