Nim 2.0+ · v1.0.0 · MIT

nimschedule

受 Python schedule 库启发,为 Nim 重新设计的任务调度库。简洁的流式 API,编译期类型安全,零运行时开销,原生线程支持。

Nim 2.2.10 兼容 ORC 内存管理 72 项测试通过

01简介

nimschedule 是一个为 Nim 语言设计的轻量级任务调度库,灵感来自 Python 的 schedule 库,但充分利用了 Nim 语言的特性:

{ }

流式 API

链式调用,代码即文档

[T]

类型安全

编译期检查,零意外

>_

原生性能

编译为 C,无 GC 压力

~

线程安全

内置锁保护,后台执行

!

错误处理

回调式异常捕获

*

多调度器

独立实例,互不干扰

相比 Python schedule 的改进

Python schedule 库需要自己写 while True: schedule.run_pending(); time.sleep(1) 循环。nimschedule 内置后台线程执行错误回调线程安全锁多调度器实例,开箱即用。

02安装

方式一:Nimble 安装

# 在项目目录中
nimble install nimschedule

方式二:直接引用源码

src/nimschedule/ 目录复制到你的项目中,然后在编译时指定路径:

# 编译时指定路径
nim c --path:src main.nim

编译要求

注意

后台执行功能需要启用线程支持,编译时必须加 --threads:on。推荐使用 ORC 内存管理(Nim 2 默认):

nim c -r --threads:on --path:src your_app.nim

03快速开始

30 秒上手 nimschedule:

import nimschedule
import std/os

# 注册一个每 2 秒运行的任务
schedule.every(2).seconds.perform:
  echo "Hello from nimschedule!"

# 主循环
while true:
  schedule.runPending()
  sleep(1000)
三种 perform 写法

nimschedule 支持三种注册任务的方式,选择你喜欢的风格:

传递函数
proc job() = echo "working"
schedule.every(10).seconds.perform(job)
do 语法 (推荐)
schedule.every(10).seconds.perform() do:
  echo "working"
代码块语法
schedule.every(10).seconds.perform:
  echo "working"
匿名 proc
schedule.every(10).seconds.perform(proc() =
  echo "working"
)
推荐

在 Nim 2.2+ 中,do: 语法是官方推荐写法。代码块语法 (perform:) 会有弃用警告,但仍可使用。

04基本调度

使用 every(N) 设置间隔数量,然后链式调用时间单位:

# 每 10 秒
schedule.every(10).seconds.perform() do:
  echo "每10秒"

# 每 5 分钟
schedule.every(5).minutes.perform() do:
  echo "每5分钟"

# 每 2 小时
schedule.every(2).hours.perform() do:
  echo "每2小时"

# 每 3 天
schedule.every(3).days.perform() do:
  echo "每3天"

# 每 2 周
schedule.every(2).weeks.perform() do:
  echo "每2周"

单数形式

every() 不传参数时默认为 1,此时可以用单数形式(语义更清晰):

# 等价于 every(1).seconds
schedule.every().second.perform(job)
schedule.every().minute.perform(job)
schedule.every().hour.perform(job)
schedule.every().day.perform(job)
schedule.every().week.perform(job)
方法单数复数说明
seconds.second.seconds秒级调度
minutes.minute.minutes分钟级调度
hours.hour.hours小时级调度
days.day.days天级调度
weeks.week.weeks周级调度

05指定时间 at()

at() 方法用于指定任务运行的具体时间点。格式取决于时间单位:

时间单位格式示例含义
HH:MMHH:MM:SS"10:30"每天 10:30
小时:MM:MM:SS:30每小时第 30 分钟
小时MM:SS15:30每小时第 15 分 30 秒
分钟:SS:15每分钟第 15 秒
星期HH:MM"09:00"该日 09:00
# 每天 10:30 运行
schedule.every().day.at("10:30").perform(job)

# 每天 10:30:00 精确到秒
schedule.every().day.at("10:30:00").perform(job)

# 每小时第 30 分钟运行
schedule.every().hour.at(":30").perform(job)

# 每小时第 15 分 30 秒
schedule.every().hour.at(":15:30").perform(job)

# 每分钟第 15 秒
schedule.every().minute.at(":15").perform(job)
at() 与 interval 的组合

every(N) 的 N > 1 时,at() 仍然有效。例如 every(3).days.at("10:30") 表示每 3 天的 10:30 运行。

06星期调度

使用星期方法来设置每周特定日期运行的任务:

# 每周一早上 9:00
schedule.every().monday.at("09:00").perform() do:
  echo "周一早会"

# 每周五下午 5:00
schedule.every().friday.at("17:00").perform() do:
  echo "周五周报"

# 每 2 周的周三
schedule.every(2).wednesday.perform() do:
  echo "双周三任务"

# 每周六和周日(分别注册)
schedule.every().saturday.perform(weekendJob)
schedule.every().sunday.perform(weekendJob)
方法对应日期
.monday周一
.tuesday周二
.wednesday周三
.thursday周四
.friday周五
.saturday周六
.sunday周日

07随机间隔 to()

to() 方法设置间隔的上限,任务将在 intervallatest 之间随机运行。适用于需要避免任务扎堆的场景。

import std/random

# 初始化随机种子
randomize()

# 每 5-10 秒之间随机运行
schedule.every(5).to(10).seconds.perform() do:
  echo "随机执行"

# 每 1-3 分钟之间随机运行
schedule.every(1).to(3).minutes.perform() do:
  echo "随机分钟任务"
注意

使用 to() 前需要调用 randomize() 初始化随机种子,否则每次运行结果相同。

08截止时间 until()

until() 方法设置任务的截止时间,到期后任务自动取消。支持三种参数类型:

字符串格式

# 今天 23:59 截止
schedule.every().minute.until("23:59").perform(job)

# 指定日期截止
schedule.every().hour.until("2025-12-31").perform(job)

# 指定日期时间截止
schedule.every().minute.until("2025-12-31 23:59:59").perform(job)

Time 对象

import std/times

# 1 小时后截止
let deadline = getTime() + initDuration(hours = 1)
schedule.every().minute.until(deadline).perform(job)

Duration 对象

# 从现在起 30 秒后截止
schedule.every(5).seconds.until(initDuration(seconds = 30)).perform() do:
  echo "30秒内每5秒运行"
支持的字符串格式

"HH:MM" · "HH:MM:SS" · "YYYY-MM-DD" · "YYYY-MM-DD HH:MM" · "YYYY-MM-DD HH:MM:SS"

09标签管理

使用 tag() 为任务添加标签,便于分组管理和批量操作:

# 添加单个标签
schedule.every().hour.perform(cpuMonitor).tag("monitor")

# 添加多个标签
schedule.every().hour.perform(memMonitor).tag("monitor", "memory")
schedule.every().day.perform(dailyReport).tag("report", "daily")

# 按标签查询
let monitorJobs = schedule.getJobs("monitor")
echo "监控任务数: ", monitorJobs.len

# 按标签清除
schedule.clear("monitor")  # 只清除带 monitor 标签的任务

# 按标签查询下次运行时间
let nextReport = schedule.nextRun("report")
链式调用顺序

tag() 返回 Job 对象,可以继续链式调用。通常放在 perform() 之后:every().hour.perform(job).tag("a", "b")

10取消任务

方式一:通过 cancelJob()

let myJob = schedule.every().second.perform(job)

# 立即从调度器中移除
schedule.cancelJob(myJob)

方式二:通过 job.cancel()

let oneShot = schedule.every().second.perform(job)

# 标记为运行后取消(下次运行后自动移除)
oneShot.cancel()

方式三:清除所有任务

# 清除所有任务
schedule.clear()

# 清除特定标签的任务
schedule.clear("monitor")

11错误处理

nimschedule 内置错误处理机制。当任务抛出异常时,不会中断调度器,而是调用错误回调:

# 设置全局错误处理器
schedule.onError(proc(e: ref Exception) =
  echo "任务出错: ", e.msg
  # 可以记录日志、发送通知等
)

# 即使这个任务会抛异常,也不会影响其他任务
schedule.every().second.perform() do:
  raise newException(ValueError, "模拟错误")

# 这个任务照常运行
schedule.every().second.perform() do:
  echo "我正常运行"
改进点

Python schedule 库默认不捕获异常,一个任务出错会中断整个调度循环。nimschedule 默认捕获所有异常,确保调度器持续运行。

12后台执行

这是 nimschedule 相比 Python schedule 的重大改进。无需手写 while True 循环,一行代码启动后台调度:

import nimschedule

# 注册任务
schedule.every(1).seconds.perform() do:
  echo "后台运行中..."

# 启动后台调度线程(每 500ms 检查一次)
startBackground(500)

# 主线程可以做其他事情
echo "主线程自由运行"
sleep(5000)  # 模拟主线程工作

# 停止后台调度
stopBackground()

检查后台状态

if isBackgroundRunning():
  echo "后台调度正在运行"
else:
  echo "后台调度已停止"
线程安全

后台线程使用 std/locks 中的 Lock 保护任务列表,确保线程安全。后台线程执行任务时使用 withLock 加锁,主线程的 runPending() 不会与后台线程冲突。

编译要求

后台执行需要线程支持,编译时必须加 --threads:on 标志。

13多调度器实例

nimschedule 支持创建多个独立的调度器实例,互不干扰:

import nimschedule
import std/locks

# 创建独立调度器
let workSched = Scheduler(jobs: @[])
initLock(workSched.lock)

let homeSched = Scheduler(jobs: @[])
initLock(homeSched.lock)

# 工作调度器
workSched.every().hour.perform() do:
  echo "[工作] 每小时检查"

# 家庭调度器
homeSched.every().day.at("20:00").perform() do:
  echo "[家庭] 晚上提醒"

# 独立运行
echo workSched.jobCount()  # 1
echo homeSched.jobCount()  # 1
echo schedule.jobCount()  # 0(默认调度器不受影响)

# 各自独立运行
workSched.runPending()
homeSched.runPending()
改进点

Python schedule 库使用全局单例,难以隔离不同模块的任务。nimschedule 的每个 Scheduler 实例完全独立。

14Nim 风格直接调用

除了 schedule.xxx 风格,nimschedule 还提供了全局便捷函数,更符合 Nim 的直接调用风格:

import nimschedule

# 不需要 schedule 前缀
discard every(2).seconds.perform(job)

# 直接调用便捷函数
runAll()           # 运行所有任务
runPending()      # 运行到期任务
echo jobCount()    # 任务数量
clear()            # 清除所有
clear("tag")      # 按标签清除
Python schedule 风格
schedule.every(10).seconds.do(job)
schedule.run_pending()
schedule.clear()
Nim 直接调用风格
every(10).seconds.perform(job)
runPending()
clear()

15API 参考

Job 方法(链式 API)

方法参数返回说明
every(N)interval: int = 1Job创建周期任务,N 为间隔数
.second(s)Job设置单位为秒
.minute(s)Job设置单位为分钟
.hour(s)Job设置单位为小时
.day(s)Job设置单位为天
.week(s)Job设置单位为周
.monday~.sundayJob设置星期调度
.at(timeStr)timeStr: stringJob指定运行时间
.to(latest)latest: intJob设置随机间隔上限
.until(...)string | Time | DurationJob设置截止时间(3 种重载)
.tag(tags)varargs[string]Job添加标签
.perform(action)JobActionJob注册并启动任务
.cancel()标记运行后取消

Scheduler 方法

方法参数返回说明
runPending()运行所有到期任务
runAll(delay)delaySeconds: float = 0立即运行所有任务
getJobs(tag)tag: string = ""seq[Job]获取任务列表
clear(tag)tag: string = ""清除任务
cancelJob(job)job: Job取消指定任务
nextRun(tag)tag: string = ""Option[Time]下次运行时间
idleSeconds()Option[float]距下次运行秒数
jobCount()int当前任务数
onError(handler)auto设置错误回调
startBackground(ms)intervalMs: int = 1000启动后台线程
stopBackground()停止后台线程

类型定义

type
  TimeUnit* = enum
    tuNone, tuSecond, tuMinute, tuHour, tuDay, tuWeek,
    tuMonday, tuTuesday, tuWednesday, tuThursday,
    tuFriday, tuSaturday, tuSunday

  JobAction* = proc() {.closure.}

  Job* = ref object
    interval*: int
    latest*: int
    unit*: TimeUnit
    atTime*: string
    tags*: HashSet[string]
    action*: JobAction
    nextRun*: Time
    lastRun*: Time
    untilTime*: Option[Time]
    cancelAfterRun*: bool

  Scheduler* = ref object
    jobs*: seq[Job]
    onErrorCb*: ErrorHandler
    bgRunning*: bool
    bgThread*: Thread[void]
    lock*: Lock

16与 Python schedule 对比

Python schedule
import schedule
import time

def job():
    print("working")

schedule.every(10).seconds.do(job)
schedule.every().day.at("10:30").do(job)

while True:
    schedule.run_pending()
    time.sleep(1)
Nim nimschedule
import nimschedule

schedule.every(10).seconds.perform() do:
  echo "working"

schedule.every().day.at("10:30").perform() do:
  echo "daily"

# 无需 while 循环!
startBackground(1000)

功能对比

功能Python schedulenimschedule
基本调度every(N).seconds.do(fn)every(N).seconds.perform(fn)
指定时间.at("10:30").at("10:30")
星期调度.monday.do(fn).monday.perform(fn)
随机间隔.to(10).do(fn).to(10).perform(fn)
截止时间.until("time").do(fn).until("time").perform(fn)
标签管理.tag("name").do(fn).tag("name").perform(fn)
错误处理需自行 try/except内置 onError 回调
后台执行需手写 while 循环内置 startBackground()
线程安全内置 Lock 保护
多调度器需 Scheduler() 类原生 Scheduler 实例
类型安全运行时编译期
性能解释执行编译为原生代码

17Nim 优势

编译期类型检查

Nim 的强类型系统在编译期捕获类型错误。JobAction 类型确保只能传递无参数闭包,避免运行时崩溃。

零开销抽象

链式 API 编译后等价于直接函数调用。{.inline.} 标记的方法在编译时内联,无额外开销。

ORC 内存管理

Nim 2 的 ORC 内存管理器自动处理引用计数,无需手动管理内存,也无需 GC 暂停。

原生线程支持

std/threads 提供原生线程,后台调度在独立线程运行,不阻塞主线程。锁保护确保线程安全。

模板元编程

perform 的代码块语法通过 Nim 模板实现,编译时展开为闭包,无运行时开销。

单文件部署

Nim 编译为单个原生可执行文件,无需运行时依赖。调度器可以直接嵌入任何 Nim 项目。

性能对比

指标Python schedulenimschedule
执行方式解释执行 (CPython)编译为 C 原生代码
内存管理GC (引用计数+分代)ORC (引用计数)
任务调度开销~微秒级~纳秒级
启动时间~100ms~1ms
二进制大小需 Python 运行时单文件 ~100KB
依赖Python 3.x无 (静态链接)
总结

nimschedule 在保持 Python schedule 库简洁 API 的同时,通过 Nim 语言的特性获得了编译期类型安全原生性能内置后台执行线程安全等优势。它是嵌入式系统、CLI 工具、服务端应用的理想调度方案。