Scala---Akka Actor(二)actor创建以及Ask模式

本文详述了Scala中Akka Actor的创建,重点探讨了Ask模式,包括其非阻塞特性、pipeTo方法的使用,以及如何在Actor系统中等待消息回应。Ask模式与Future结合,通过pipeTo将结果传递给其他actor,确保了消息处理的顺序性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

在本文中,我们将对actor的创建进行介绍,并主要讲解一下Ask模式。虽然在Actor中,更受欢迎的是Tell模式,但是我们还是有必要了解一下稍微复杂一点的Ask模式。


一、Actor的创建

一个actor对象是由actorOf()方法创建的,需要一个Props参数和一个名字(字符串)。

object DemoActor {
  /**
   * Create Props for an actor of this type.
   *
   * @param magicNumber The magic number to be passed to this actor’s constructor.
   * @return a Props for creating this actor, which can then be further configured
   *         (e.g. calling `.withDispatcher()` on it)
   */
  def props(magicNumber: Int): Props = Props(new DemoActor(magicNumber))
}

class DemoActor(magicNumber: Int) extends Actor {
  def receive = {
    case x: Int => sender() ! (x + magicNumber)
  }
}

class SomeOtherActor extends Actor {
  // Props(new DemoActor(42)) would not be safe
  context.actorOf(DemoActor.props(42), "demo")
  // ...
}
上面是Akka官方推荐的构建方式,一个class用来定义Actor,一个object用来提供props方法。Props的内容会在之后的学习中,在进行详细的研究。


二、Ask模式

我们在上面的代码中,可以看到如下的消息传递方式:

sender() ! (x + magicNumber)
在这里,一个actor在receive函数中,向消息的源actor发送消息,使用tell(符号为:!)。这就是Tell模式,这个模式理解起来很简单,就是给别的actor发送一个消息,之后不阻塞,不记忆(事情没有发生过的样子)。没有等待消息回应的机制,接收到消息的actor往往会使用sender()方法,向消息的传递者回应消息。

而我们要讲的Ask模式,需要等待一个消息回应。而在等待回应的时候,我们可以定义一个等待的时间。我们看一下ask的函数签名:

def ask(message: Any)(implicit timeout: Timeout, sender: ActorRef = Actor.noSender): Future[Any]
我们可以使用一个implicit变量来自动填充timeout域。
package actors_1

import akka.actor.{Actor, ActorSystem, Props}

import scala.concurrent.duration._
import akka.pattern.ask
import akka.util.Timeout

import scala.concurrent.ExecutionContext.Implicits.global
import scala.util.{Failure, Success}


class NoRespond extends Actor{
  override def receive: Receive = Actor.emptyBehavior
}

object AskNoRespond extends App{

  val system = ActorSystem("AskNoRespond")

  val actor = system.actorOf(Props[NoRespond], "norespond")

  implicit val timeout = Timeout(2 second)
·
  (actor ? "Hello") andThen {
    case Success(_) => println("success")
    case Failure(err) => println((s"error: $err"))
  }
}
在代码中,我们设置timeout为2000ms,令actor对所有消息都不处理。我们可以得到如下的错误:

error: akka.pattern.AskTimeoutException: Ask timed out on [Actor[akka://AskNoRespond/user/norespond#-1340240777]] after [2000 ms]. Sender[null] sent message of type "java.lang.String".
由于在2000ms内,actor没有回复,所以Ask模式返回的Future不再阻塞,从而得到一个Failure(),注意这个Failure在scala.util包下。如果成功的话,大家可以自行对NoRespond类的receive函数进行修改。

现在我们介绍一下Ask模式中,经常使用的pipeTo方法。

import akka.pattern.{ ask, pipe }
import system.dispatcher // The ExecutionContext that will be used
final case class Result(x: Int, s: String, d: Double)
case object Request

implicit val timeout = Timeout(5 seconds) // needed for `?` below

val f: Future[Result] =
  for {
    x <- ask(actorA, Request).mapTo[Int] // call pattern directly
    s <- (actorB ask Request).mapTo[String] // call by implicit conversion
    d <- (actorC ? Request).mapTo[Double] // call by symbolic name
  } yield Result(x, s, d)

f pipeTo actorD // .. or ..
pipe(f) to actorD
上面的代码是Akka官方给出的一个实例,我们就用这个进行讲解。使用for来构造Result对象。在for的初始化域当中,分别使用3种不同的方式调用ask()方法。对得到的Future对象使用mapTo转换成对应的类型。最终使用x,y,d构造Result。

上述的全部过程都是非阻塞的,所以在Result还没有生成的时候,Result对象f由pipeTo函数传递给另一个actor(actorD)。当Result组装完成(出现超时),onComplete handler将其通过消息传递给actorD。

以下的代码是稍微修改上面的代码,代码能够执行:

package actors_1

import akka.actor.Status.Failure
import akka.actor.{Actor, ActorIdentity, ActorLogging, ActorSystem, Identify, Props}
import akka.pattern.{ask, pipe}
import akka.util.Timeout

import scala.concurrent.ExecutionContext.Implicits.global
import scala.concurrent.duration._

final case class Result(x: Int, s: String, d: Double)

case object Request

class Mediator extends Actor with ActorLogging {
  implicit val timeout = Timeout(5 seconds)
  val actorA = context.actorOf(Props[A1], "actorA")
  val actorB = context.actorOf(Props[A2], "actorB")
  val actorC = context.actorOf(Props[A3], "actorC")

  override def receive: Receive = {
    case Request =>
      (for {
        x <- (actorA ? Request).mapTo[Int]
        y <- (actorB ? Request).mapTo[String]
        d <- (actorC ? Request).mapTo[Double]
      } yield Result(x, y, d)).pipeTo(sender())

      log.info(s"Answer an request from $sender")
  }
}

class A1 extends Actor with ActorLogging {
  var value = 10

  override def receive: Receive = {
    case Request =>
      sender() ! value
      value += 1
  }
}

class A2 extends Actor {
  var value = "Hello"

  override def receive: Receive = {
    case Request =>
      sender() ! value
      value += "i"
  }
}

class A3 extends Actor {
  var value = 10.23

  override def receive: Receive = {
    case Request =>
      sender() ! value
      value += 1
  }

  //  override def receive: Receive = Actor.emptyBehavior
}

class Origin extends Actor with ActorLogging {
  override def receive: Receive = {
    case Request =>
      context.actorSelection("/user/med") ! Identify(1)
    case ActorIdentity(1, Some(ref)) =>
      ref ! Request
    case r@Result(_, _, _) => log.info(s"$r")
    case Failure(err) => log.error(s"$err")
    case _ => log.error("Unexpected Massage")
  }
}

object AskDemo extends App {
  val system = ActorSystem("AskDemo")

  val origin1 = system.actorOf(Props[Origin], "origin1")

  val origin2 = system.actorOf(Props[Origin], "origin2")

  val mediator = system.actorOf(Props[Mediator], "med")

  origin1 ! Request

  origin2 ! Request
}
在上面的代码中,我们创建了两个Origin来发起Request。它们分别向同一个Mediator发送Request消息。(这里使用了actorSelection用来进行actor的查找)Mediator在初始化的时候创建了3个子actor用来计算(A1,A2,A3)。他们分别计算结果,并通过pipeTo传递给对应的origin。
[INFO] [07/28/2017 23:19:29.347] [AskDemo-akka.actor.default-dispatcher-4] [akka://AskDemo/user/med] Answer an request from Actor[akka://AskDemo/user/origin2#-1912347482]
[INFO] [07/28/2017 23:19:29.348] [AskDemo-akka.actor.default-dispatcher-4] [akka://AskDemo/user/med] Answer an request from Actor[akka://AskDemo/user/origin1#577293972]
[INFO] [07/28/2017 23:19:29.355] [AskDemo-akka.actor.default-dispatcher-4] [akka://AskDemo/user/origin1] Result(11,Helloi,11.23)
[INFO] [07/28/2017 23:19:29.355] [AskDemo-akka.actor.default-dispatcher-2] [akka://AskDemo/user/origin2] Result(10,Hello,10.23)
我们可以从输出得到如下结论:

1. 前两个Info当中,ask、mapTo、pipeTo操作全部是非阻塞的。

2. 子actor(A1,A2,A3)返回的结果都直接被传递到对应的origin当中,并且由于消息的局部顺序性,所以orgin1和origin2得到的结果是一致的。


三、总结

在本文中,我们对Actor中的Ask模式进行了介绍。由于其中使用了一些Future方面的知识,所以需要读者自行补充一下。(主要指导andThen和mapTo方法的用法)。其中Ask模式和pipeTo经常一起使用,能够直接将Future放到另一个actor中进行等待(Futre的阻塞)。而pipeTo的实现还需要进一步的学习和研究。

Feel free to point out my mistakes.






评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值