事件等待(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 事件属性只收脚本赋值引发的 (旧值, 新值)。
