阿卡:回应的顺序

时间:2015-11-21 23:19:27

标签: scala akka

我的演示应用很简单。这是演员:

class CounterActor extends Actor {
  @volatile private[this] var counter = 0

  def receive: PartialFunction[Any, Unit] = {
    case Count(id)     ⇒ sender ! self ? Increment(id)
    case Increment(id) ⇒ sender ! {
      counter += 1
      println(s"req_id=$id, counter=$counter")
      counter
    }
  }
}

主要应用:

sealed trait ActorMessage
case class Count(id: Int = 0) extends ActorMessage
case class Increment(id: Int) extends ActorMessage

object CountingApp extends App {

  // Get incremented counter
  val future0 = counter ? Count(1)
  val future1 = counter ? Count(2)
  val future2 = counter ? Count(3)
  val future3 = counter ? Count(4)
  val future4 = counter ? Count(5)

  // Handle response
  handleResponse(future0)
  handleResponse(future1)
  handleResponse(future2)
  handleResponse(future3)
  handleResponse(future4)

  // Bye!
  exit()
}

我的经纪人:

def handleResponse(future: Future[Any]): Unit = {
  future.onComplete {

    case Success(f) => f.asInstanceOf[Future[Any]].onComplete {
      case x => x match {
        case Success(n) => println(s" -> $n")
        case Failure(t) => println(s" -> ${t.getMessage}")
      }
    }

    case Failure(t) => println(t.getMessage)
  }
}

如果我运行应用程序,我会看到下一个输出:

req_id=1, counter=1
req_id=2, counter=2
req_id=3, counter=3
req_id=4, counter=4
req_id=5, counter=5
 -> 4
 -> 1
 -> 5
 -> 3
 -> 2

处理回复的顺序是随机的。这是正常的行为吗?如果不是,我该如何订购?

PS

我是否需要演员中的volatile var?

PS2

另外,我正在为handleResponse寻找一些更方便的逻辑,因为这里的匹配非常含糊......

2 个答案:

答案 0 :(得分:3)

正常行为?

是的,这绝对是正常行为。

您的演员按照您发送的顺序收到计数增量,但期货正在通过提交到底层线程池完成。这是Future-thread绑定的不确定排序,导致println执行失序。

如何订购?

如果你想要有序执行Futures,那么它就是同步编程的同义词,即根本没有并发性。

我需要挥发性吗?

Actor的状态只能在Actor本身中访问。这就是为什么Actor的用户永远不会得到一个真正的Actor对象,他们只得到一个ActorRef,例如val actorRef = actorSystem actorOf Props[Actor]。这部分是为了确保Actors的用户永远不能通过消息传递更改Actor的状态。来自docs

  

好消息是,Akka演员在概念上各有自己的   轻质螺纹,完全与其余部分隔离   系统。这意味着不必使用同步访问   锁定你可以编写你的演员代码而不用担心   并发性。

因此,您不需要挥发性。

更方便的逻辑

为了更方便的逻辑,我建议使用Agents,它是一种带有更简单消息框架的类型化Actor。来自docs

import scala.concurrent.ExecutionContext.Implicits.global
import akka.agent.Agent

val agent = Agent(5)

val result = agent()

val result = agent.get

agent send 7

agent send (_ + 1)

读取是同步但即时的。写作是异步的。这意味着无论何时进行读取,您都不必担心Futures,因为内部值立即返回。但是一定要阅读文档,因为排队逻辑可以使用更复杂的技巧。

答案 1 :(得分:2)

您的方法中的实际问题不是异步性质,而是过于复杂的逻辑。

尽管拉蒙的答案非常明确,但是我确定有一些方法可以确保akka某些部分的秩序。我们可以从the doc读取,每个发件人 - 收件人对 保证 消息排序。

这意味着,对于两个演员的每个单向频道都有保证,消息将按照发送的顺序发送。

但是,对于您用来处理答案的Future任务完成顺序,没有这样的保证。将Futureask作为消息发送给原始发件人是很奇怪的。

你可以这样做: 将您的Increment重新定义为

case class Increment(id: Int, requester: ActorRef) extends ActorMessage

所以处理程序可以知道原始请求者

修改CounterActor的接收为

def receive: Receive = {
    case Count(id) ⇒ self ! Increment(id, sender)
    case Increment(id, snd) ⇒ snd ! {
      counter += 1
      println(s"req_id=$id, counter=$counter")
      counter
    }
  }

handleResponse简化为

  def handleResponse(future: Future[Any]): Unit = {
    future.onComplete {
      case Success(n: Int) => println(s" -> $n")

      case Failure(t) => println(t.getMessage)
    }
  }

现在您可能会看到以相同的顺序收到消息。

我说可能因为处理仍然在Future.onComplete中发生,所以我们需要另一个演员来确保订单。

让我们定义额外的消息

case object StartCounting

演员本身:

class SenderActor extends Actor {
  val counter = system.actorOf(Props[CounterActor])

  def receive: Actor.Receive = {
    case n: Int => println(s" -> $n")
    case StartCounting =>
      counter ! Count(1)
      counter ! Count(2)
      counter ! Count(3)
      counter ! Count(4)
      counter ! Count(5)
  }
}

在您的main中,您现在可以写

val sender = system.actorOf(Props[SenderActor])

sender ! StartCounting

扔掉handleResponse方法。

现在你肯定应该以正确的顺序看到你的消息处理。

我们实现了整个逻辑,没有单askthat's good

如此神奇的规则是:将处理回应留给演员,只通过ask获得最终结果。

注意还有forward method,但这会创建代理actor,因此消息排序将再次被破坏。