我一直在使用Akka,而且在更简单的项目中,只使用Scala 2.10期货。然而,有一天我不得不混合一个图书馆返回Futures和Akka Actors,我不知道如何整合Actor调度系统和失控的未来。
在项目中使用Akka actor的选择是能够微调actor队列并控制代码的并行化,并且可能最终向外扩展(即使现在它不是优先级)。但是,如果我有这样的代码(故意简化):
package externallib
object DoSomething {
def foo(): Future[Something] = ....
}
package myapp
class MyActor extends Actor {
def receive = {
case "message" => externallib.DoSomething.foo() pipeTo sender
}
}
.../
val actorRef = system.actorOf(Props[MyActor].withRouter(
RoundRobinRouter(nrOfInstances = 5)))
(0 to 20000) foreach {
val futureSomething = actorRef ? "message"
}
然后MyActor实际上没有做任何事情,除了在externallib中生成一个线程并直接返回。 externallib future最终会将结果传递给actor的调用者。
然而,通过这种方式,管理actor的良好控制的路由器并不真正控制生成的线程,因为它们是在actor系统之外生成的,即使在相同的ExecutionContext中也是如此。在大循环的示例中,这意味着不是将消息排队到actor,而是将这些消息快速消耗并在任何严格控制的队列之外产生20000个线程。
我在想我可以这样做:
class MyActor extends Actor {
def receive = {
case "message" =>
val res = Await.result(externallib.DoSomething.foo(), someDuration)
sender ! res
}
}
这将确保在externallib完成(或超时)之前不会向actor发送新的"message"
。但是,这实际上可能需要两个线程(actor的一个和DoSomething中的一个)等待一次计算。
是否有更好的方法来控制在演员系统之外产生的这些未来?
答案 0 :(得分:0)
MyActor看起来很危险,阻止actor内部并不是一个好主意,事实上,如果ExecutionContext中只有一个线程,那么actor和将来使用它将会死锁。写这个的更好方法是在未来放置一个onComplete
,并让演员对一个行为进行become
,使其不会发送任何新的未来(可能是stash
所有收到的消息)。未来的onComplete
既可以向发件人发送消息,也可以通知消息者告诉become
原始行为。