Akka演员,期货和关闭

时间:2012-06-21 00:23:59

标签: scala closures actor akka future

我在Akka docs中读到,关闭封闭演员的变量是危险的。

  

警告

     

在这种情况下,你需要小心避免关闭   包含actor的引用,即不要调用方法   从匿名Actor类中包含actor。这个会   打破actor封装并可能引入同步错误   和竞争条件,因为将安排其他演员的代码   与封闭演员同时发生。

现在,我有两个演员,其中一个从第二个请求某些东西并对结果做了一些事情。在下面的这个例子中,我把演员 Accumulator 从演员 NumberGenerator 中检索出来并将它们相加,然后报告总和。

这可以通过至少两种不同的方式完成,因为此示例显示了两个不同的接收功能( A vs B )。两者之间的区别在于 A 不会关闭计数器变量;相反,它等待一个整数并将其相加,而 B 创建一个 Future ,它关闭计数器并完成总和。如果我正确理解它是如何工作的,那么这是在为处理onSuccess而创建的匿名actor中发生的。

import com.esotericsoftware.minlog.Log

import akka.actor.{Actor, Props}
import akka.pattern.{ask, pipe}
import akka.util.Timeout
import akka.util.duration._

case object Start
case object Request


object ActorTest {
  var wake = 0

  val accRef = Main.actorSystem.actorOf(Props[Accumulator], name = "accumulator")
  val genRef = Main.actorSystem.actorOf(Props[NumberGenerator], name = "generator")

  Log.info("ActorTest", "Starting !")

  accRef ! Start
}

class Accumulator extends Actor {
  var counter = 0

  implicit val timeout = Timeout(5 seconds)

  // A: WITHOUT CLOSURE
  def receive = {
    case Start => ask(ActorTest.genRef, Request).mapTo[Int] pipeTo self
    case x: Int => counter += x; Log.info("Accumulator", "counter = " + counter); self ! Start
  }
  // B: WITH CLOSURE
  def receive = {
    case Start => ask(ActorTest.genRef, Request).mapTo[Int] onSuccess {
      case x: Int => counter += x; Log.info("Accumulator", "counter = " + counter); self ! Start
    }
  }
}

class NumberGenerator extends Actor {
  val rand = new java.util.Random()

  def receive = {
    case Request => sender ! rand.nextInt(11)-5
  }
}

在这种情况下使用闭包绝对是邪恶的吗?当然我可以使用AtomicInteger而不是Int,或者在某些网络场景中使用netty,在threadsafe频道上发出写操作,但这不是我的观点。

冒着被问到荒谬的风险:未来的onSuccess是否有办法在这个演员而不是匿名中间演员中执行,没有来定义案例接收功能?

修改

更清楚地说,我的问题是:有没有办法迫使一系列Futures在与给定Actor相同的线程中运行?

2 个答案:

答案 0 :(得分:5)

问题是onSuccess将在与演员receive将要运行的线程不同的线程中运行。您可以使用pipeTo方法,或使用一个Agent。使counter成为AtomicInteger可以解决问题,但它不是那么干净 - 也就是说,它打破了Actor模型。

答案 1 :(得分:5)

实现此类设计的最简单方法是使用“即发即忘”语义:

class Accumulator extends Actor {
  private[this] var counter = 0

  def receive = {
    case Start => ActorTest.genRef ! Request
    case x: Int => {
      counter += x
      Log.info("Accumulator", "counter = " + counter)
      self ! Start
    }
  }
}

此解决方案完全异步,您不需要任何超时。