Skip to Content

事件等待(Event Await)

await <事件源> 在异步函数里挂起当前帧,直到该事件下次触发,并把载荷取回来。等待的本质是一次性订阅:触发即恢复、恢复即出册,不产生订阅句柄,与 ~> 订阅互不干扰。

产物 = handler 的参数表

await 的产物与该事件 handler 收到的参数按同一规则打包:handler 收什么,await 就产什么。

事件声明纯 await 产物带 cancel / timeout
零参 event Started();void(只能语句形)bool(true=等到,false=取消或超时)
单参 event Solo(x: int);int(裸值)int?
多参 event Pair(a: int, b: string);(int, string) 元组(int, string)?
事件属性 event prop Level: int(int, int)(旧值, 新值)(int, int)?
event Solo(x: int); event Pair(a: int, b: string); event Started(); async func main() -> int { spawn { await 5ms; emit Solo(7); }; val v = await Solo; print("solo:", v); spawn { await 5ms; emit Pair(1, "a"); }; val (x, y) = await Pair; print("pair:", x, y); spawn { await 5ms; emit Started(); }; await Started; print("started"); return 0; }
solo: 7 pair: 1 a started
  • 多参产物是元组,调用点直接解构。
  • 零参没有载荷,只能作语句等待。

取消通道即可空通道

带 cancel ct 或 timeout D 的 await,会发生产物可空化:取消或超时的时刻帧继续往下跑,await 表达式的值就是 null,defer 照常执行。

timeout 等于一个自动的 ct:死线到点即取消,事件先到则拿真值,先到者赢。预取消(await 之前令牌已取消)立即以 null 完成,不注册等待。

import Async.*; event Tick(n: int); event Ping(); async func main() -> int { spawn { await 20ms; emit Tick(9); }; val q: int? = await Tick timeout 5ms; print(q == null); val r: int? = await Tick timeout 5s; print(r == 9); val cts = new CancellationTokenSource(); cts.Cancel(); val b: bool = await Ping cancel cts.Token; print(b); return 0; }
True True False

纯 await(不带通道)产物非空,也没有取消唤醒:spawn cancel ct 对睡在事件等待上的帧只置取消位、不唤醒,与 await Duration 完全对称;取消是否发生靠 ct.IsCancellationRequested(cancellable 体内为 cancel.requested)自查。

等一个永远不来的事件会一直挂起,推进由宿主泵决定,这是「等一个可能不来的事件」的本义。

目标面

操作数镜像订阅位:声明名、成员事件、事件属性、局部持有的事件值、eventof(...) 皆可作 await 的目标。

事件属性与 computed 的产物恒为 (旧值, 新值),由 setter 管线自动 emit,与订阅 handler 的收参约定一致:

event Solo(x: int); class Door { public event Opened(at: int); public func Open() { emit Opened(3); } } class Box { public event prop Level: int { get; set; } } async func main() -> int { val b = new Box(); val lev = eventof(b.Level); spawn { await 5ms; b.Level = 42; }; val (o, n) = await lev; print(o, n); val d = new Door(); spawn { await 5ms; d.Open(); }; val at = await d.Opened; print(at); val ev = Solo; spawn { await 5ms; emit Solo(7); }; print(await ev); return 0; }
0 42 3 7

带通道的可空产物走判空分支消费,判空收窄对可空元组生效,分支内直接解构:

event Pair(a: int, b: string); async func main() -> int { spawn { await 5ms; emit Pair(1, "a"); }; val pay: (int, string)? = await Pair timeout 1s; if (pay != null) { val (m, n) = pay; print(m, n); } return 0; }
1 a

恢复时机与语义

emit 先同步跑完全部订阅 handler(含管线 finally),之后按等待登记顺序先进先出逐个恢复等待帧,handler 同步观察事件,awaiter 随后被唤醒。多帧等待同一事件各自独立送达,顺序即登记顺序:

event Hit(n: int); func H(n: int) -> void { print("h", n); } async func main() -> int { Hit ~> H; spawn { val n1 = await Hit; print("w", 1, n1); }; spawn { val n2 = await Hit; print("w", 2, n2); }; await 10ms; emit Hit(7); return 0; }
h 7 w 1 7 w 2 7
  • 无缓冲:只数挂起之后的下一次触发,await 之前已触发的永不补发。
  • 一次性:每次 await 都是独立的一次性订阅,恢复即出册,同一帧多次 await 同一事件各等各的。
  • 不是订阅:不产生 subscription 句柄,不影响 Cancel / IsCancelled / TriggerCount 三件套(见订阅句柄与事件值)。
  • await 可缀 meanwhile { … } 块:先布网再内联跑块,块内 emit 本事件即自触发快径、全程无挂起,见 meanwhile 块缀。
  • 位置约束照旧:await 只能在 async 函数或 spawn 体内,事件 handler lambda 体内不可 await。
  • 宿主侧的 emit 桥同样唤醒等待帧,「宿主触发事件、脚本 await 等待」成立。

注意

  • 取消零错误路径:取消与超时不报错、不抛异常,产物位 null 而已,零参事件给 false。
  • tween 直写组件不经 setter 管线,不触发事件属性通知,await 事件属性只收脚本赋值引发的 (旧值, 新值)。
最后更新于 2026年10月10日