☰
仓颉 std.sync 定时器 Timer 完全指南:一次性与重复性任务调度的 API 解析与实战
2026/10/15 3:34:41 网站建设 项目流程
  • 人工智能
  • AI 技能
  • 文档
  • 开发工具

【免费下载链接】DocAgent

项目地址:https://gitcode.com/Cangjie-SIG/DocAgent
点击查看免费下载

本文以仓颉标准库std.sync包中的Timer类(API 文档)为核心,系统讲解其在"指定时间点或时间间隔后执行任务"场景下的完整用法:一次性任务与重复性任务的四种创建方式、after返回值驱动的动态调度机制、CatchupStyle三种追平策略的选型,以及cancel取消与生命周期管理。读完本文,你将掌握在仓颉并发程序中正确使用Timer创建、控制和回收定时任务的全套能力,并理解其"不保证准时、保证不晚于当前时间"等底层语义。

Timer 是什么:并发包中的定时器抽象

Timer是仓颉标准库std.sync包提供的定时器功能,用于在指定时间点或指定时间间隔后,执行指定任务(Task)一次或多次。从类声明(见 sync_package_classes.md)看:

public class Timer <: Equatable<Timer> & Hashable

它实现了Equatable<Timer>与Hashable两个接口,因此Timer实例支持基于"同一实例"的相等比较与哈希,可用作集合元素。

在使用之前,需要先理解Timer的几个关键语义约束,它们直接影响你的程序行为:

  • 隐式 spawn:每个Timer在创建时会隐式包含一次spawn操作,即每个Timer会创建一个仓颉线程(仓颉线程是 M:N 用户态轻量线程,详见 sync_package_overview.md)用于执行该 Timer 关联的 Task。
  • 单 Task 绑定:每个Timer只能在初始化时绑定一个 Task,初始化完成后无法重置关联的 Task。
  • 生命周期与 GC:只有关联 Task 执行完毕,或通过cancel接口主动取消 Timer,Timer 的生命周期才会结束,之后才能被 GC 回收。换句话说,在 Task 执行完毕或被主动取消之前,Timer 实例不会被 GC 回收,从而确保关联 Task 能被正常执行。
  • 时间保证:系统繁忙时 Task 的触发时间可能被影响。Timer 不保证 Task 的触发时间一定准时,但保证 Task 的触发时间小于等于当前时间。
  • 异常语义:Timer 不会主动捕获关联 Task 抛出的异常;只要 Task 有未被捕获的异常,该 Timer 就会失效。

按使用方式,Timer 通常分为一次性任务定时器(Task 只执行一次)和重复性任务定时器(Task 按指定周期执行,直到主动cancel或达到创建时指定的结束条件)。

创建一次性定时任务:Timer.once

once用于设置并启动一次性定时任务,返回控制这个任务的Timer对象实例:

public static func once(delay: Duration, task: ()->Unit): Timer

参数说明:

  • delay:从现在开始到 Task 被执行的时间间隔,取值范围[Duration.Min, Duration.Max];小于或等于Duration.Zero时 Task 将立即被执行。
  • task:待定时执行的任务(无参、无返回值的 lambda)。

官方 API 文档中的示例(sync_package_classes.md):

import std.time.MonoTime main() { let start = MonoTime.now() Timer.once(Duration.second, {=> println("Tick at: ${MonoTime.now() - start}") }) sleep(Duration.second * 2) 0 }

运行结果(不同机器会有微小偏差):

Tick at: 1s2ms74us551ns

可以看到,Task 在约 1 秒后触发且仅触发一次。Duration是仓颉标准库std.core中表示时间间隔的结构体,提供nanosecond、millisecond、second、minute、hour、day等静态常量,以及Zero、Min、Max三个边界常量(详见 Duration_struct.md),上述所有时长参数均以它为单位。

创建重复性定时任务:repeat / repeatDuring / repeatTimes

重复性任务定时器有三种创建方式,分别对应"无条件周期执行、限时执行、限次执行"三种结束条件。

repeat:按固定周期重复执行

public static func repeat(delay: Duration, interval: Duration, task: ()->Unit, style!: CatchupStyle = Burst): Timer
  • delay:从现在开始到 Task 首次被执行的时间间隔,取值范围[Duration.Min, Duration.Max],小于或等于Duration.Zero时立即执行。
  • interval:两次 Task 之间的时间间隔,取值范围(Duration.Zero, Duration.Max]。
  • task:待定时执行的任务。
  • style!:追平策略,默认Burst(详见下文 CatchupStyle 追平策略)。
  • 异常:当interval小于等于Duration.Zero时抛出IllegalArgumentException(异常类文档)。

官方示例(sync_package_classes.md):

import std.sync.{Timer} import std.time.{MonoTime} main() { let start = MonoTime.now() let timer = Timer.repeat(Duration.second, Duration.second, {=> println("Tick at: ${MonoTime.now() - start}") }) sleep(Duration.second * 5) timer.cancel() 0 }

运行结果:

Tick at: 1s2ms72us188ns Tick at: 2s4ms185us160ns Tick at: 3s6ms275us464ns Tick at: 4s8ms18us399ns Tick at: 5s9ms621us394ns

注意repeat创建的任务没有"自动结束"条件,必须由调用方持有返回的Timer实例,在合适时机调用cancel()停止调度。

repeatDuring:限定重复周期的最大持续时间

public static func repeatDuring(period: Duration, delay: Duration, interval: Duration, task: () -> Unit, style!: CatchupStyle = Burst): Timer
  • period:重复周期的最大持续时间,从delay之后开始计时,取值范围(Duration.Zero, Duration.Max]。
  • delay、interval、task、style!:含义与repeat相同。
  • 异常:当period小于等于Duration.Zero,或interval小于等于Duration.Zero时,抛出IllegalArgumentException。

官方示例(sync_package_classes.md):

import std.sync.{Timer} import std.time.{MonoTime} main() { let start = MonoTime.now() Timer.repeatDuring(Duration.second * 5, Duration.second, Duration.second, {=> println("Tick at: ${MonoTime.now() - start}") }) sleep(Duration.second * 7) 0 }

运行结果:

Tick at: 1s2ms372us626ns Tick at: 2s4ms714us879ns Tick at: 3s6ms769us623ns Tick at: 4s8ms780us235ns Tick at: 5s660us104ns Tick at: 6s3ms257us508ns

从输出可见,任务从第 1 秒开始每 1 秒触发一次;到达第 5 秒的周期上限后,定时器在下一周期节点(约第 6 秒)还会再执行一次以"收尾"——周期结束后不会立即中断正在排程的最后一次任务,这与"最大持续时间"的语义一致。周期结束后 Timer 自动失效,无需手动取消。

repeatTimes:限定最大执行次数

public static func repeatTimes(count: Int64, delay: Duration, interval: Duration, task: () -> Unit, style!: CatchupStyle = Burst): Timer
  • count:Task 最大执行次数,取值范围(0, Int64.Max]。
  • delay、interval、task、style!:含义同repeat。
  • 异常:当count <= 0,或interval小于等于Duration.Zero时,抛出IllegalArgumentException。

官方示例(sync_package_classes.md):

import std.sync.{Timer} import std.time.{MonoTime} main() { let start = MonoTime.now() Timer.repeatTimes(3, Duration.second, Duration.second, {=> println("Tick at: ${MonoTime.now() - start}") }) sleep(Duration.second * 4) 0 }

运行结果:

Tick at: 1s2ms855us251ns Tick at: 2s5ms390us18ns Tick at: 3s7ms935us552ns

Task 恰好执行 3 次后定时器自动失效,无需手动取消。

返回值驱动的动态调度:Timer.after

after是四种创建方式中唯一由 Task 返回值决定"是否继续、间隔多久"的动态调度接口:

public static func after(delay: Duration, task: () -> Option<Duration>): Timer

调度规则如下(sync_package_classes.md):

  • 如果定时器第一次触发的时间点小于当前时间,关联 Task 会立刻被调度执行;
  • 如果关联 Task 的返回值为Option.None,该 Timer 将会失效,并停止调度关联 Task;
  • 如果关联 Task 的返回值为Option.Some(v)且v大于Duration.Zero,下次运行前的最小时间间隔将被设置为v;
  • 否则(返回Some(v)且v <= Duration.Zero),关联 Task 会立刻再次被调度执行。

其中Option<T>是仓颉标准库中"可能有值也可能无值"的封装类型,Some携带一个值、None不带值(详见 Option_enum.md)。after非常适合"下一轮间隔取决于本轮结果"的场景,例如心跳、指数退避重试等。

以下是一个演示after语义的示意性用法(前两次返回Some(500 * Duration.millisecond),第三次返回None让 Timer 自动失效):

import std.sync.{Timer} import std.time.MonoTime main() { let start = MonoTime.now() var times = 0 Timer.after(Duration.second, { => times += 1 println("Tick at: ${MonoTime.now() - start}, times = ${times}") if (times >= 3) { None // 返回 None,Timer 失效并停止调度 } else { Some(500 * Duration.millisecond) // 下次运行前的最小时间间隔设为 500ms } }) sleep(Duration.second * 3) 0 }

需要注意的是,该示例仅用于说明after的控制流逻辑;Task内的times变量访问处于定时器线程与主线程之间,若在真实并发场景中需要跨线程共享计数,应改用AtomicInt64等原子类型(参见下文综合示例),以保证数据同步正确性。

CatchupStyle 追平策略:三种策略的语义与选型

重复性任务在"Task 执行时间大于设定触发间隔"时会产生时间点堆积。CatchupStyle枚举定义了三种追平策略(枚举文档):

public enum CatchupStyle { | Delay | Burst | Skip }
策略语义适用场景
Burst(默认)每个任务的开始时间间隔固定;当任务执行时间大于设定的任务触发间隔时间时,依次执行错过的时间点上的任务,直到追平。定时任务之间有先后关系、需要依次执行任务的场景。
Delay上一次任务结束与下一次任务开始的时间间隔总是固定的,即下一次任务的开始时间 = 上一次任务的结束时间 + 设定的任务触发间隔时间。不关心任务开始的时间点,需要两次任务执行之间的时间间隔固定的场景。
Skip每个任务的开始时间间隔固定;当任务执行时间大于设定的任务触发间隔时间时,跳过后面错过的时间点,以尽快追平。定时任务之间没有先后关系、可以忽略中间任务的场景。

style!是命名参数(带!后缀),默认值为Burst。三种创建重复任务的接口均可通过命名参数指定,例如 sync_package_enums.md 中给出的Delay策略示例:

import std.sync.{Timer, CatchupStyle} import std.time.{MonoTime} main() { let start = MonoTime.now() Timer.repeatTimes(3, Duration.second, Duration.second, {=> println("Tick at: ${MonoTime.now() - start}") }, style: Delay) sleep(Duration.second * 4) 0 }

选型建议:若任务间存在严格先后依赖(如链式数据处理),用Burst;若只需保证"两次任务间隔恒定"(如周期性采样),用Delay;若错过的时间点可以丢弃(如刷新缓存、定期汇总最新状态),用Skip。

控制与查询成员:cancel、hashCode 与相等比较

cancel:主动取消定时器

public func cancel(): Unit

取消该 Timer 后,关联 Task 将不再被调度执行。cancel的语义细节(sync_package_classes.md):

  • 如果调用时关联 Task 正在执行,不会打断当前运行;
  • 该函数不会阻塞当前线程;
  • 调用该函数多次等同于只调用一次。

这意味着cancel只负责"停止未来调度",对正在执行的 Task 采取放行策略,因此可以在任意线程安全地调用它来终止重复性任务(如repeat创建的定时器)。

hashCode 与相等比较

public func hashCode(): Int64

获取 Timer 对象的哈希值,返回Int64。此外,Timer还通过Equatable<Timer>提供了两个运算符重载:

  • operator func ==(rhs: Timer): Bool:判断当前 Timer 与rhs是否是同一个实例;
  • operator func !=(rhs: Timer): Bool:判断两个 Timer 是否不是同一个实例。

注意这是实例同一性比较,而非内容相等性比较,这与Timer作为"任务控制器"的语义一致。

参数取值范围与异常速查表

综合上述 API,各参数的有效范围与异常条件汇总如下(均以官方文档为准):

接口参数取值范围异常条件
oncedelay[Duration.Min, Duration.Max];<= Zero立即执行无
repeatdelay[Duration.Min, Duration.Max];<= Zero立即执行interval <= Zero抛IllegalArgumentException
repeatinterval(Duration.Zero, Duration.Max]同上
repeatDuringperiod(Duration.Zero, Duration.Max]period <= Zero或interval <= Zero抛异常
repeatTimescount(0, Int64.Max]count <= 0或interval <= Zero抛异常

综合实战:一次性 + 重复性任务的组合示例

sync_samples.md 提供了一个同时使用once与repeat的完整可运行示例,展示了如何借助AtomicInt8在定时器线程与主线程之间安全地传递计数:

import std.sync.* main(): Int64 { let count = AtomicInt8(0) Timer.once(50 * Duration.millisecond) { => println("run only once") count.fetchAdd(1) } let timer = Timer.repeat(100 * Duration.millisecond, 200 * Duration.millisecond, { => println("run repetitively") count.fetchAdd(10) }) sleep(Duration.second) timer.cancel() sleep(500 * Duration.millisecond) println("count = ${count.load()}") 0 }

运行结果:

run only once run repetitively run repetitively run repetitively run repetitively run repetitively count = 51

结果解读:一次性任务在 50ms 后执行一次(count加 1);重复任务首次触发于 100ms,之后每 200ms 触发一次,1 秒内共执行 5 次(count加 10×5);timer.cancel()后不再有新的触发,count最终为 51。该示例同时演示了三个关键实践:

  1. 持有Timer引用:repeat创建的定时器必须保存返回值,才能在其生命周期内调用cancel;
  2. 原子类型跨线程传值:定时器 Task 运行在独立的仓颉线程中,与主线程共享状态时应使用AtomicInt8/AtomicInt64等原子类型(见 AtomicInt64_class.md)保证可见性与同步;
  3. sleep用于主线程等待:仓颉并发程序中常用std.time的sleep(Duration)让主线程等待定时器触发完毕(Duration相关常量见 Duration_struct.md)。

生命周期与内存管理要点

最后,把Timer的生命周期规则归纳为以下四条实践准则,避免踩坑:

  • 创建即运行:四个静态工厂方法在调用时即完成"初始化并启动",无需额外启动操作;
  • 结束条件三选一:Task 执行完毕(once/repeatTimes)、到达周期上限(repeatDuring)、主动cancel,任一发生即结束调度;
  • 未取消前不会被 GC:由于隐式spawn与单 Task 绑定语义,只要 Timer 还在调度,其关联 Task 就保证能被执行,因此程序退出前对不再需要的重复性定时器应显式cancel,以便及时释放资源;
  • 异常会杀死 Timer:Timer 不捕获 Task 异常,Task 内任何未捕获异常都会导致该 Timer 失效,因此建议在 Task 内部自行try/catch兜底,避免一个异常中断整个定时链路。

至此,你已经掌握了仓颉std.sync定时器的全部核心 API:从once的一次性执行,到repeat/repeatDuring/repeatTimes的三种重复模式,再到after的动态间隔调度与CatchupStyle追平策略选型,以及cancel与生命周期管理。相关 API 细节可继续查阅 Timer_class.json(机器可读索引)与 sync_package_overview.md(并发模型背景)。

  • 人工智能
  • AI 技能
  • 文档
  • 开发工具

【免费下载链接】DocAgent

项目地址:https://gitcode.com/Cangjie-SIG/DocAgent
点击查看免费下载

相关推荐

上一篇:Waza 中文去 AI 味改写指南:从识味到改写的完整规则与实践
下一篇:免费手机号码归属地定位工具:输入 11 位号码,一步在地图上标出城市

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询