Actor内部调用带返回值的方法,能否将返回值作为消息传出到外部存储?
结论
你可以将Future[String]作为消息从Actor中发回给调用方,也可以存储到外部变量,但你当前的写法无法实现这个需求,原因是Akka Actor的receive方法返回值固定为Unit,你在case分支中返回的Future并不会被Actor自动发送给消息发送者,需要你手动处理。
正确实现方式
方式1:直接将Future作为消息返回
你需要先手动捕获当前消息的发送方引用,再主动将Future发送给对方:
class RandomActor @Inject() ( accounts: models.Accounts ) extends Actor with ActorLogging{ // 注意:type是Scala关键字,作为参数名需要加反引号转义,或者修改为其他参数名比如accountType override def receive: Receive = { case Create(address, username, `type`, password) => // 提前捕获发送方引用,避免异步闭包中sender指向错误 val replyTo = sender() val createFuture = accounts.Service.create(address, username, `type`, password) // 主动将Future作为消息发回 replyTo ! createFuture } }
外部调用方通过ask模式发消息,就能拿到这个Future并存储到外部变量:
import akka.pattern.ask import akka.util.Timeout import scala.concurrent.duration._ import scala.concurrent.Future implicit val timeout: Timeout = 5.seconds // 注意要处理类型转换,flatten用来把嵌套的Future[Future[String]]拍平为Future[String] val externalResult: Future[String] = (randomActorRef ? Create(addr, username, accountType, password)) .mapTo[Future[String]] .flatten
方式2(更推荐):异步等待Future完成后返回结果
更符合Akka开发习惯的做法是不直接传递Future本身,而是等Future执行完成后将实际结果发回,避免嵌套Future的处理成本:
override def receive: Receive = { case Create(address, username, `type`, password) => val replyTo = sender() accounts.Service.create(address, username, `type`, password) .onComplete { tryResult => // 将执行结果(成功的String或异常)封装为Try发回,也可以单独处理成功/失败返回不同消息 replyTo ! tryResult }(context.dispatcher) // 用Actor上下文的dispatcher执行回调,不要用全局ExecutionContext }
外部调用时直接就能拿到结果:
val externalResult: Future[String] = (randomActorRef ? Create(addr, username, accountType, password)) .mapTo[scala.util.Try[String]] .flatMap(Future.fromTry)
注意事项
- 绝对不要在Actor内部用
Await.result等方法阻塞等待Future完成,会占用Actor的调度线程,严重影响性能甚至导致死锁。 - 不要在Future的异步回调中直接使用
sender(),Actor的sender()是随处理的消息动态变化的,等回调执行时sender()可能已经指向其他消息的发送方,必须提前捕获为局部变量再使用。 - Future本身是线程安全的不可变句柄,作为Akka消息传递不存在线程安全问题,符合Akka的消息不可变要求。
内容的提问来源于stack exchange,提问作者bahdotsh
相关产品推荐
相关产品推荐

