Skip to Content

事件(Event)

event 声明一个广播频道:emit 一次发射,所有订阅者依次收到载荷。事件可以挂在模块顶层,也可以挂在类型上成为实例事件。

声明与发射

event Ticked(n: int); event Ping();

载荷是位置参数表,emit 频道(实参); 发射事件。订阅用 ~>,退订用 !~>(未订阅也安全):

event Ticked(n: int); func OnTick(n: int) { print("收到", n); } func OnTick2(n: int) { print("二号", n); } Ticked ~> OnTick; Ticked ~> OnTick2; emit Ticked(5); Ticked !~> OnTick2; emit Ticked(6);
收到 5 二号 5 收到 6

退订后只剩一个订阅者。<~ 是反向书写(OnPing <~ Ping; 等价 Ping ~> OnPing),适合「谁消费谁订阅」的阅读顺序。

派发顺序与去重

派发按订阅顺序(先订阅先派发),同步完成:emit 的调用栈里逐个执行 handler,与声明顺序无关:

event Ticked(n: int); Ticked ~> (n: int) => print("h2", n); Ticked ~> (n: int) => print("h1", n); Ticked ~> (n: int) => print("h3", n); emit Ticked(1);
h2 1 h1 1 h3 1

同一 handler 对同一事件重复订阅会去重:只占一个槽位、派发一次,一次 !~> 即全部移除。

重入

handler 执行中再 emit(同一事件或别的)是合法的:内层立即递归派发,栈式嵌套,内层完整跑完才回到外层。派发中途退订自己不影响本轮,自己从下一轮起不再收到:

event A(n: int); A ~> (n: int) => { print("enter", n); if n < 2 { emit A(n + 1); } print("exit", n); }; emit A(1);
enter 1 enter 2 exit 2 exit 1

handler 的形状

handler 可以是具名函数或 lambda。返回值不受约束,声明 -> int 也合法,返回值被忽略。

Note

订阅 handler 须是普通函数:无 fail、无 uses、非 async。

实例事件

事件挂在类上就是实例事件,派发表按实例隔离:

class Counter { public event Changed(from: int, to: int); public var value: int = 0; public func Set(v: int) { val old = value; value = v; emit Changed(old, v); } } func OnCh(from: int, to: int) { print("chg", from, "->", to); } val g = new Counter(); g.Changed ~> OnCh; g.Set(1); g.Changed !~> OnCh; g.Set(2);
chg 0 -> 1

实例事件的退订 g.Changed !~> h 与顶层同款。实例丢弃最后引用后订阅是否自动失效由宿主 GC 决定,语言不做承诺,句柄的 IsCancelled 不因此变为 True。

事件属性

事件属性(event prop)更进一步:写入即广播,载荷固定是 (旧值, 新值),且带变化判断,第二次 = 5 没有产生广播:

class Gauge { public event prop Level: int; } val g = new Gauge(); g.Level ~> (oldV: int, newV: int) => { print($"液位 {oldV} -> {newV}"); }; g.Level = 5; g.Level = 5; g.Level = 8;
液位 0 -> 5 液位 5 -> 8

变化判断发生在 before 管线改写之后。同值写入跳过的是整条写链,存储、after 段与自动 emit 全部跳过,只有 finally 恒跑,且与有没有订阅者无关(管线段语法见事件管线)。

泛型事件

event Changed<T>(v: T); 仅顶层可声明,实例化即通道。类型实参三路可推:显式实例化(Changed<int> ~> onInt)、handler 形参推断(Changed ~> (v: int) => ...)、emit 实参推断(emit Changed(43);)。

不同实例化是不同频道:Changed<int> 的订阅者收不到 emit Changed("s"),两个频道互不相通,也不报错。

注意

  • 事件也是 await 的操作数:await <事件源> 一次性等待下次触发并取回载荷,见事件等待。
  • 事件带 before / after / finally 管线段与写位八步序,见事件管线。
  • 订阅产出的 subscription 句柄面见订阅句柄与事件值。
最后更新于 2026年10月10日