切换到不同的行为不会按预期工作
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 中的 Receive
是 Behavior
而不是 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
中有多个子 Behavior
s/Receive
s 的情况下,这种差异很重要(当 AbstractBehavior
中有一个 Receive
时并不重要).
TL;DR:如果在 AbstractBehavior
中定义多个消息处理程序,在消息处理程序中更喜欢 return Behaviors.same
而不是 return this
。或者:只为每个 AbstractBehavior
.
定义一个消息处理程序
所以我在 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 中的 Receive
是 Behavior
而不是 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
中有多个子 Behavior
s/Receive
s 的情况下,这种差异很重要(当 AbstractBehavior
中有一个 Receive
时并不重要).
TL;DR:如果在 AbstractBehavior
中定义多个消息处理程序,在消息处理程序中更喜欢 return Behaviors.same
而不是 return this
。或者:只为每个 AbstractBehavior
.