行为切换与状态机

Akka Typed使用Behavior替代了Actor,通过函数式的方式来构建消息处理行为,每次消息处理后都需要返回下一个(消息处理)行为。经典(Untyped)actor的becomeunbecomeFSM (状态机)都不再需要了,因为通过返回下一个行为时就已经可以实现以上功能。

下面定义了一个简单的状态机actor,它有两个状态行为:idleactive,分别由passiverunning两个事件消息来触发切换。

object FiniteStateMachine {
  def apply(): Behavior[String] = Behaviors.setup { context =>
    new FiniteStateMachine(context).idle()
  }
}

class FiniteStateMachine private (context: ActorContext[String]) {
  private val pendingMessages = mutable.Queue.empty[String]

  def idle(): Behavior[String] = Behaviors.receiveMessage {
    case "running" =>
      active()
    case msg =>
      context.log.info(s"[idle] receive message: $msg")
      pendingMessages enqueue msg
      Behaviors.same
  }

  def active(): Behavior[String] = Behaviors.receiveMessage {
    case "passive" =>
      idle()
    case msg =>
      context.log.info(s"[active] receive message: $msg")
      processPendingMessages()
      Behaviors.same
  }

  private def processPendingMessages(): Unit = {
    while (pendingMessages.nonEmpty) {
      val msg = pendingMessages.dequeue()
      context.log.info(s"Process pending message: $msg")
    }
  }
}

这里使用了FiniteStateMachine类的方式来定义Behavior行为集,通过定义不同的类函数来实现在不同事件消息情况下的行为,这样还可通过类属性来保存actor的内部状态。初始时在Behavior.setup构造块里使用了idle这个行为函数,当收到running消息时通过返回active()函数实现了行为切换,就类似经典actor的become一样。

当你的actor有多个行为函数,或代码逻辑(行)比较多时,通过这种 的方式来管理它们能使行代码更加清晰,这是一种良好的实践!

Note

可以在FiniteStateMachine类构造块里作actor的初始化工作,也可以在Behaviors.setup里初始化后将数据通过构造参数传给FiniteStateMachine,就像context参数一样。

但需要注意的时,如何actor重启,pendingMessages将不会保存之前的状态(内容),因为FiniteStateMachine将被重新实例化。这可以通过某种持久化机制来解决,如: Akka Persistence ;或者在 PreRestart 信号处理函数里自行保存数据,再在类构造时读出。

测试代码如下:

val ref = spawn(FiniteStateMachine(), "fsm")
ref ! "message 1"
ref ! "message 2"
ref ! "running"
ref ! "message 3"
ref ! "message 4"
ref ! "passive"
ref ! "message 5"
在此文档中发现错误?该页面的源代码可以在 这里 找到。欢迎随时编辑并提交Pull Request。