Akka (Scala) - 如何正确重置计时器/调度程序?

Akka (Scala) - how to properly reset timer / scheduler?

我必须填写模板以实现购物车超时。到目前为止我有:

import akka.actor.{Actor, ActorRef, Cancellable, Props, Timers}
import akka.event.{Logging, LoggingReceive}

import scala.concurrent.duration._
import scala.language.postfixOps

object CartActor {
  sealed trait Command
  case class AddItem(item: Any) extends Command
  case class RemoveItem(item: Any) extends Command
  case object ExpireCart extends Command
  case object StartCheckout extends Command
  case object ConfirmCheckoutCancelled extends Command
  case object ConfirmCheckoutClosed extends Command

  sealed trait Event
  case class CheckoutStarted(checkoutRef: ActorRef) extends Event

  def props = Props(new CartActor())
}

class CartActor extends Actor with Timers {
  import context._
  import CartActor._

  private val log = Logging(context.system, this)
  val cartTimerDuration: FiniteDuration = 5 seconds

  var cart: Cart = Cart.empty

  private def scheduleTimer: Cancellable =
    system.scheduler.scheduleOnce(cartTimerDuration, self, ExpireCart)

  def receive: Receive = empty

  def empty: Receive = {
    case AddItem(item) =>
      this.cart = cart.addItem(item)
      scheduleTimer
      context become nonEmpty(cart, scheduleTimer)
    case _ =>
  }

  def nonEmpty(cart: Cart, timer: Cancellable): Receive = {
    case AddItem(item) =>
      this.cart = cart.addItem(item)
      timer.cancel()
      scheduleTimer
    case RemoveItem(item) =>
      this.cart = this.cart.removeItem(item)
      if (this.cart.size != 0) {
        timer.cancel()
        scheduleTimer
      }
      else
        context become empty
    case StartCheckout =>
      context become inCheckout(this.cart)
    case ExpireCart =>
      this.cart = Cart.empty
      println("Cart expired")
      context become empty
  }

  def inCheckout(cart: Cart): Receive = {
    case ConfirmCheckoutCancelled =>
      context become nonEmpty(cart, scheduleTimer)
    case ConfirmCheckoutClosed =>
      println("Cart closed after checkout")
      context become empty
    case _ =>
  }
}

提供了方法签名,例如我无法更改 def nonEmpty(cart: Cart, timer: Cancellable)。添加或删除项目时,应重置计时器,以便用户再次有 5 秒的时间做某事。问题是我不知道如何正确地做到这一点——上面的方法显然不会重置计时器,因为它总是在 5 秒后超时。 我怎样才能做到这一点?我应该使用计时器而不是调度程序,例如timers.startSingleTimer("ExpireCart", ExpireCart, cartTimerDuration)?我应该如何在方法之间传递它?计时器是否应该改为 CartActor 的属性,而我应该忽略调度程序?附带一提,当我有 def nonEmpty(cart: Cart, timer: Cancellable) 时,计时器是在任何地方隐式调用,还是只是传递?

有两个问题。

首先,在 empty 中您启动了两个计时器:

  scheduleTimer // Creates a timer, reference lost so can't be cancelled
  context become nonEmpty(cart, scheduleTimer) // Creates a second timer

更一般地说,您在接收方法中使用可变状态 参数。不需要可变状态,因此删除此行:

var cart: Cart = Cart.empty

现在修复 nonEmpty 以将更新后的状态传递给新的 receive 方法,而不是使用 this:

def nonEmpty(cart: Cart, timer: Cancellable): Receive = {
  case AddItem(item) =>
    timer.cancel()
    context become nonEmpty(cart.addItem(item), scheduleTimer)

  case RemoveItem(item) =>
    timer.cancel()
    if (this.cart.size > 1) {
      context become nonEmpty(cart.remoteItem(item), scheduleTimer)
    } else {
      context become empty
    }
  case StartCheckout =>
    timer.cancel()
    context become inCheckout(cart)
  case ExpireCart =>
    timer.cancel() // Just in case :)
    println("Cart expired")
    context become empty
}