Rust Task Scheduler: A Complete Guide for Developers
Task schedulers are an essential part of modern development. They allow you to automate routine operations: periodic cache cleanup, sending notifications, data synchronization, and much more. In the Rust ecosystem, there are several powerful libraries for implementing schedulers. In this article, we will cover three main approaches: cron, tokio-cron-scheduler, and job_scheduler.
1. Basic Scheduler with the cron Library
The cron library provides minimalistic functionality for parsing cron expressions and calculating the next run time. It does not contain a built-in execution loop, so it is often combined with tokio or std::thread.
Installation:
[dependencies]cron = "0.12"chrono = "0.4"tokio = { version = "1", features = ["full"] }Example of a simple scheduler:
use cron::Schedule;use chrono::Utc;use std::str::FromStr;use std::thread;use std::time::Duration;
fn main() { // Every minute let expression = "0 * * * * *"; let schedule = Schedule::from_str(expression).unwrap();
loop { let now = Utc::now(); if let Some(next) = schedule.upcoming(Utc).next() { let duration = next - now; if duration.num_seconds() > 0 { thread::sleep(Duration::from_secs(duration.num_seconds() as u64)); } println!("[{}] Task executed!", Utc::now()); } }}This approach gives you full control but requires manual loop management. It is suitable for simple scenarios.
2. Async Scheduler tokio-cron-scheduler
For modern async applications based on tokio, the tokio-cron-scheduler library is ideal. It provides a ready-made scheduler with support for complex cron schedules, jobs with identifiers, and error handlers.
Installation:
[dependencies]tokio-cron-scheduler = "0.10"tokio = { version = "1", features = ["full"] }Example with two tasks:
use tokio_cron_scheduler::{Job, JobScheduler};use std::sync::Arc;
#[tokio::main]async fn main() -> Result<(), Box<dyn std::error::Error>> { let sched = JobScheduler::new().await?;
// Task 1: every 5 seconds let job1 = Job::new_async("*/5 * * * * *", |_uuid, _lock| { Box::pin(async move { println!("[Job1] Executed at {:?}", std::time::SystemTime::now()); }) })?;
// Task 2: every minute let job2 = Job::new_async("0 * * * * *", |_uuid, _lock| { Box::pin(async move { println!("[Job2] Minute task"); }) })?;
sched.add(job1).await?; sched.add(job2).await?; sched.start().await?;
// Keep the application running tokio::signal::ctrl_c().await?; println!("Scheduler stopped"); Ok(())}Advantages: automatic time management, support for deleting/pausing tasks, non-blocking async execution.
3. Simple Scheduler job_scheduler
The job_scheduler library is focused on maximum simplicity. It does not require an async runtime and is suitable for console utilities or embedding into existing synchronous projects.
Installation:
[dependencies]job_scheduler = "1.2"chrono = "0.4"Example:
use job_scheduler::{Job, JobScheduler};use std::time::Duration;
fn main() { let mut sched = JobScheduler::new();
sched.add(Job::new("1/10 * * * * *".parse().unwrap(), || { println!("Executing task every