在我之前的博文中,已经介绍过要慎用Actor的ask。这里我们要分析一下ask的源码,看看它究竟是怎么实现的。

  开发时,如果要使用ask方法,必须要引入akka.pattern._,这样才能使用ask(或者?)方法,那么想必ask是在akka.pattern._对应的包里面实现的。

  1. /*
  2. * Implementation class of the “ask” pattern enrichment of ActorRef
  3. */
  4. final class AskableActorRef(val actorRef: ActorRef) extends AnyVal {
  5.  
  6. /**
  7. * INTERNAL API: for binary compatibility
  8. */
  9. protected def ask(message: Any, timeout: Timeout): Future[Any] =
  10. internalAsk(message, timeout, ActorRef.noSender)
  11.  
  12. def ask(message: Any)(implicit timeout: Timeout, sender: ActorRef = Actor.noSender): Future[Any] =
  13. internalAsk(message, timeout, sender)
  14.  
  15. /**
  16. * INTERNAL API: for binary compatibility
  17. */
  18. protected def ?(message: Any)(implicit timeout: Timeout): Future[Any] =
  19. internalAsk(message, timeout, ActorRef.noSender)
  20.  
  21. def ?(message: Any)(implicit timeout: Timeout, sender: ActorRef = Actor.noSender): Future[Any] =
  22. internalAsk(message, timeout, sender)
  23.  
  24. /**
  25. * INTERNAL API: for binary compatibility
  26. */
  27. private[pattern] def internalAsk(message: Any, timeout: Timeout, sender: ActorRef) = actorRef match {
  28. case ref: InternalActorRef if ref.isTerminated
  29. actorRef ! message
  30. Future.failed[Any](new AskTimeoutException(s"""Recipient[$actorRef] had already been terminated. Sender[$sender] sent the message of type "${message.getClass.getName}"."""))
  31. case ref: InternalActorRef
  32. if (timeout.duration.length <= 0)
  33. Future.failed[Any](new IllegalArgumentException(s"""Timeout length must be positive, question not sent to [$actorRef]. Sender[$sender] sent the message of type "${message.getClass.getName}"."""))
  34. else {
  35. val a = PromiseActorRef(ref.provider, timeout, targetName = actorRef, message.getClass.getName, sender)
  36. actorRef.tell(message, a)
  37. a.result.future
  38. }
  39. case _ Future.failed[Any](new IllegalArgumentException(s"""Unsupported recipient ActorRef type, question not sent to [$actorRef]. Sender[$sender] sent the message of type "${message.getClass.getName}"."""))
  40. }
  41.  
  42. }

  上面是通过定位ask(或?)找到的实现源码,我们发现,这是一个隐式转换,在akka.pattern.AskSupport中我们找到了隐式转换对应的函数。

  1. implicit def ask(actorRef: ActorRef): AskableActorRef = new AskableActorRef(actorRef)

  通过AskableActorRef源码我们知道最终调用了internalAsk函数,该函数有三个参数:待发送的消息、超时时间、消息发送者。函数创建了一个PromiseActorRef,又把消息原样的通过原actor的tell函数发送给了原actor,然后用新创建的PromiseActorRef作为新的sender传递,再用PromiseActorRef的result.future作为最终的Future返回。下面我们来看一下PromiseActorRef的创建过程。

  1. def apply(provider: ActorRefProvider, timeout: Timeout, targetName: Any, messageClassName: String,
  2. sender: ActorRef = Actor.noSender, onTimeout: String Throwable = defaultOnTimeout): PromiseActorRef = {
  3. val result = Promise[Any]()
  4. val scheduler = provider.guardian.underlying.system.scheduler
  5. val a = new PromiseActorRef(provider, result, messageClassName)
  6. implicit val ec = a.internalCallingThreadExecutionContext
  7. val f = scheduler.scheduleOnce(timeout.duration) {
  8. result tryComplete Failure(
  9. onTimeout(s"""Ask timed out on [$targetName] after [${timeout.duration.toMillis} ms]. Sender[$sender] sent message of type "${a.messageClassName}"."""))
  10. }
  11. result.future onComplete { _ try a.stop() finally f.cancel() }
  12. a
  13. }

  很明显,PromiseActorRef持有了一个Promise[Any],但是上面的代码只显示了在超时的时候通过onTimeout赋了值,并没有不超时赋值的逻辑,且Promise[Any]一旦完成就调用PromiseActorRef的stop方法和cancel方法。那么成功时赋值的逻辑应该在哪里呢?

  如果你对ask的使用方式比较熟悉的话,一定会找出其中的端倪的。我们来梳理一下这其中的使用细节。其实,在ask的使用与tell,并没有太大的区别,至少对于server端的Actor来说没有任何区别,都是正常的接收消息,然后处理,最后通过sender把消息使用tell返回。在ask时,消息的sender是什么,就是PromiseActorRef啊,那PromiseActorRef的!方法具体怎么实现的呢?

  1. override def !(message: Any)(implicit sender: ActorRef = Actor.noSender): Unit = state match {
  2. case Stopped | _: StoppedWithPath provider.deadLetters ! message
  3. case _
  4. if (message == null) throw InvalidMessageException("Message is null")
  5. if (!(result.tryComplete(
  6. message match {
  7. case Status.Success(r) Success(r)
  8. case Status.Failure(f) Failure(f)
  9. case other Success(other)
  10. }))) provider.deadLetters ! message
  11. }

  很明显,接收消息的Actor会通过!返回对应的消息,消息的处理一般会命中 case other,这其实就是给result赋值,在超时之前的赋值。如果在!方法内部给result赋值的时候,刚好已经超时或已经赋过值,会把返回的消息发送给deadLetters。

  其实result,也就是Promise[Any]的赋值逻辑已经解释清楚。不过如果小伙伴对Promise不熟悉的话,此处还是有点难理解的。如果说Future是一个只读的,值还没计算的占位符。那么Promise就是一个可写的,单次指派的容器,也就是说Promise一旦赋值,就无法再次赋值,且与之关联的future也就计算完毕,返回的值就是固定的。当然了通过ask(也就是?)返回的还只是一个future,如果要取出future最终的值,还是需要Await.ready等语义来支持的,这里就不再详细解释了。

Akka源码分析-ask模式的更多相关文章

  1. 鸿蒙内核源码分析(工作模式篇) | CPU是韦小宝,七个老婆 | 百篇博客分析OpenHarmony源码 | v36.04

    百篇博客系列篇.本篇为: v36.xx 鸿蒙内核源码分析(工作模式篇) | CPU是韦小宝,七个老婆 | 51.c.h .o 硬件架构相关篇为: v22.xx 鸿蒙内核源码分析(汇编基础篇) | CP ...

  2. Akka源码分析-Akka-Streams-概念入门

    今天我们来讲解akka-streams,这应该算akka框架下实现的一个很高级的工具.之前在学习akka streams的时候,我是觉得云里雾里的,感觉非常复杂,而且又难学,不过随着对akka源码的深 ...

  3. Akka源码分析-Cluster-Metrics

    一个应用软件维护的后期一定是要做监控,akka也不例外,它提供了集群模式下的度量扩展插件. 其实如果读者读过前面的系列文章的话,应该是能够自己写一个这样的监控工具的.简单来说就是创建一个actor,它 ...

  4. Akka源码分析-Cluster-Distributed Publish Subscribe in Cluster

    在ClusterClient源码分析中,我们知道,他是依托于“Distributed Publish Subscribe in Cluster”来实现消息的转发的,那本文就来分析一下Pub/Sub是如 ...

  5. Akka源码分析-Persistence

    在学习akka过程中,我们了解了它的监督机制,会发现actor非常可靠,可以自动的恢复.但akka框架只会简单的创建新的actor,然后调用对应的生命周期函数,如果actor有状态需要回复,我们需要h ...

  6. Akka源码分析-local-DeathWatch

    生命周期监控,也就是死亡监控,是akka编程中常用的机制.比如我们有了某个actor的ActorRef之后,希望在该actor死亡之后收到响应的消息,此时我们就可以使用watch函数达到这一目的. c ...

  7. Akka源码分析-Cluster-ActorSystem

    前面几篇博客,我们依次介绍了local和remote的一些内容,其实再分析cluster就会简单很多,后面关于cluster的源码分析,能够省略的地方,就不再贴源码而是一句话带过了,如果有不理解的地方 ...

  8. Akka源码分析-Akka Typed

    对不起,akka typed 我是不准备进行源码分析的,首先这个库的API还没有release,所以会may change,也就意味着其概念和设计包括API都会修改,基本就没有再深入分析源码的意义了. ...

  9. Akka源码分析-Cluster-Singleton

    akka Cluster基本实现原理已经分析过,其实它就是在remote基础上添加了gossip协议,同步各个节点信息,使集群内各节点能够识别.在Cluster中可能会有一个特殊的节点,叫做单例节点. ...

随机推荐

  1. 大数低速幂运算模板(c++)+python大数幂

    简介 自己从大数加法改过来的模板,低速计算n的t次幂,n,t小于等于100速度能够保证 模板 #include <bits/stdc++.h> using namespace std; s ...

  2. nginx代理标准配置

    #nginx开启的进程数worker_processes   4;     #4核CPU   #定义全局错误日志定义类型,[debug|info|notice|warn|crit]error_log  ...

  3. python3连接mysql 稍微进阶 + 日期处理

    1.踩了个操作中文的坑,结果发现之前的文章中有强调了,在连接处加:charset="utf8" conn = pymysql.connect(host = '127.0.0.1', ...

  4. Python-函数和代码复用

    函数的定义与使用 >函数的理解与定义 函数是一段代码的表示 -函数是一段具有特定功能的.可重用的语句组 -函数是一种功能的抽象,一般函数表达特定功能 -两个作用:降低编程难度 和 代码复用 de ...

  5. MyBatis启动:MapperStatement创建

    参考:http://blog.csdn.net/ashan_li/article/details/50351080 MappedStatement说明 一个MappedStatement对象对应Map ...

  6. android调试

    要进行调试,首先构建app的时候必须选择是Debug模式,而不能是Release模式. 接下来的内容转载自: http://www.cnblogs.com/gaoteng/p/5711314.html ...

  7. 洛谷—— P1725 琪露诺

    https://www.luogu.org/problem/show?pid=1725 题目描述 在幻想乡,琪露诺是以笨蛋闻名的冰之妖精.某一天,琪露诺又在玩速冻青蛙,就是用冰把青蛙瞬间冻起来.但是这 ...

  8. Java设计模式补充:回调模式、事件监听器模式、观察者模式(转)

    一.回调函数 为什么首先会讲回调函数呢?因为这个是理解监听器.观察者模式的关键. 什么是回调函数 所谓的回调,用于回调的函数. 回调函数只是一个功能片段,由用户按照回调函数调用约定来实现的一个函数. ...

  9. Java虚拟机深入JVM内核—原理、诊断与优化视频教程

    http://www.eimhe.com/forum.php?mod=viewthread&tid=142832&highlight=%C4%DA%BA%CB

  10. 复习es6-解构赋值+字符串的扩展

    1. 数组的解构赋值 从数组中获得变量的值,给对应的声明变量赋值,,有次序和对应位置赋值 解构赋值的时候右边必须可以遍历 解构赋值可以使用默认值 惰性求值,当赋值时候为undefined时候,默认是个 ...