通知规则

订阅者在被通知的过程中订阅、退订或回写时会发生什么。

更新于 2026-09-08

大多数时候,订阅者只是读一下值然后更新点什么。这一页讲的是剩下的情况:订阅者在自己的回调里改动订阅者列表,或者改动那个值本身。

订阅者不能回写自己的 observable

下面这段编译不过:

score.Subscribe(Clamp);

private void Clamp(int value)
{
    if (value > 10)
        score.Value = 10;      // UDN0012
}

Udonite 会拒绝它。订阅者又碰回自己的 observable,等于经由委托闭合了一个环;而 Udon 无法为间接调用保存栈帧,于是生成出来的程序不能正确返回。

被拒绝才是好结果。它拦下的形状里也包括更不显眼的那一种:回去的那一跳是属性 setter 而不是方法。真让这种代码跑起来的世界并不会很快出错,它会一直冻着,直到 VRChat 用 Program execution time exceeded max VM time of 10,0 seconds 把 behaviour 掐掉,留下的是冻了十秒的世界和一条没有用的信息。

改成赋值前先夹紧:

score.Value = raw > 10 ? 10 : raw;

或者写到另一个 observable 上,那是完全正常的做法。

通知过程中的退订从下一次开始生效

订阅者可以在回调里退订自己,也可以退订别人。正在进行的这一轮通知仍然按开始时的订阅者走完,退订从下一次通知起生效。

private void Once(int value)
{
    score.Unsubscribe(Once);      // 没问题,其余的照样会被调用,
}                                 // 而 Once 不会再被调用

这和 C# 事件是一样的。 移除订阅者会新建一份列表,而不是改动正在遍历的那一份,这也正是能在回调里退订而不出事的原因。

所以正在被拆掉的 behaviour 可能还会被调用一次,就在已经跑起来的那一轮里。如果某个处理器在对象消失后绝对不能再跑,请在处理器里自己挡住,而不要指望退订:

private void OnScoreChanged(int value)
{
    if (closing)
        return;

    label.text = value.ToString();
}

通知过程中的订阅要等下一次

在通知进行途中加进来的处理器,本轮不会被调用。它并没有错过任何它在场时发生的事,而在它刚加入的那一轮里就被调用反而是意外。

订阅两次,第二次什么也不做

变成订阅两次的常见路径是 Start 又跑了一遍。每次变化被调用两次一定是 bug,所以第二次订阅被忽略而不是被采纳。

通知所有人,和通知一个人

Publish() 会通知每一个订阅者,不管有没有东西改变。

如果只想让一个迟到的订阅者跟上进度,改成给 Subscribetrue。用 Publish 也能做到,但那会毫无理由地让其他所有订阅者重绘一遍,也会让任何在计数的订阅者数错。

score.Subscribe(OnScoreChanged, true);   // 只有这一个处理器
score.Publish();                          // 全部

读取永远是安静的

CountContainsIndexOfToArrayContainsKeyTryGetValue 以及所有 getter 都不通知任何人。只有改变才会。

SetSilentlyAddSilently 改值而不通知,用来放一个订阅者本就没道理去反应的初始值,比如进入世界时恢复的分数。

出了点问题 重新加载