切换到不同的行为不会按预期工作

Switching to different behaviors does not work as intended

所以我在 Akka 中尝试不同的行为。当我执行这段代码时:

@Override
public Receive<CommonCommand> createReceive() {
    return notYetStarted();
}

public Receive<CommonCommand> notYetStarted() {
    return newReceiveBuilder()

            .onMessage(RaceLengthCommand.class, message -> {
                
                // business logic

                return running();
            })

            .build();
}

public Receive<CommonCommand> running() {
    return newReceiveBuilder()

            .onMessage(AskPosition.class, message -> {

                if ("some_condition") {

                    // business logic
                    
                    return this;

                } else {

                    // business logic

                    return completed(completedTime);
                }

            })

            .build();

}

public Receive<CommonCommand> completed(long completedTime) {
    return newReceiveBuilder()

            .onMessage(AskPosition.class, message -> {
                
                // business logic
                
                return this;
            })

            .build();

}

我得到以下日志:

21:46:41.038 [monitor-akka.actor.default-dispatcher-6] INFO akka.actor.LocalActorRef - Message [learn.tutorial._5_racing_game_akka.RacerBehavior$AskPosition] to Actor[akka://monitor/user/racer_1#-1301834398] was unhandled. [1] dead letters encountered. This logging can be turned off or adjusted with configuration settings 'akka.log-dead-letters' and 'akka.log-dead-letters-during-shutdown'.

最初 RaceLengthCommand 消息被发送到 notYetStarted() 行为。那很好用。然后这个行为应该转换为 running() 行为,第二个应该收到消息 AskPosition.

但根据我的测试,AskPosition 消息被传递给 notYetStarted() 行为。这与我对这个概念的整体理解相矛盾。

我通过复制 running() 行为中的 onMessage() 部分并粘贴到 notYetStarted() 行为来确认这一点。现在代码执行得很好,没有死信了。

显然 notYetStarted() 行为确实在我切换行为后仍能接收消息? 为什么会这样???

您的 actor 定义似乎混合了 OO 和功能样式,并且在同一 actor 中使用这些样式之间的相互作用中出现了一些缺陷。这种混淆的部分原因是 Java API 中的 ReceiveBehavior 而不是 AbstractBehavior。这可能是 API 早期演变的遗迹(我已经向 Akka 维护者建议在 2.7 中删除此遗迹(由于二进制兼容性,这是最早可以删除的遗迹);与其中一些还没有给出这种区别的原因,在类似的 Scala API).

中也没有这种区别。

免责声明:我倾向于专门使用 Scala 函数 API 来定义角色。

在 actor 的状态(即它的字段)和它的行为(它如何响应它收到的下一条消息)之间的 actor 模型中存在二元性:对于 actor 之外的世界,它们是一个并且相同是因为观察 actor 状态的唯一方法(忽略像进行堆转储这样的事情)是观察它对消息的响应。当然,实际上,当前行为是actor运行时表示中的一个字段,而在函数定义中,行为一般都有state的字段。

OO 风格的行为定义有利于:

  • actor 中的可变字段
  • 当前行为是不可变的(行为可以根据字段做出决定)

行为定义的函数式风格偏爱:

  • actor 中没有可变字段
  • 更新行为(这是一个隐式可变字段)

(区别类似于具有 while/for 循环的命令式编程,其中更新变量与函数式编程偏好定义递归函数,编译器在幕后将其转换为循环)。

AbstractBehavior API 似乎假定消息处理程序是 createReceive():在 AbstractBehavior 中返回 this 意味着返回到 createReceive()。相反,函数式风格中的 Behaviors.same() 表示“无论当前行为是什么,都不要改变它”。在一个 AbstractBehavior 中有多个子 Behaviors/Receives 的情况下,这种差异很重要(当 AbstractBehavior 中有一个 Receive 时并不重要).

TL;DR:如果在 AbstractBehavior 中定义多个消息处理程序,在消息处理程序中更喜欢 return Behaviors.same 而不是 return this。或者:只为每个 AbstractBehavior.

定义一个消息处理程序