Akka/Scala: mapping Future vs pipeTo

2020-06-08 20:06发布

问题:

In Akka actors, are there any differences - in terms of number of threads being used, or thread locking - between sending a Future result to another actor by:

A. mapping the Future to function that tell the result to the actor.

B. defining an onSuccess callback on the future, which tell the result to the actor.

C. piping the Future result to the actor with pipeTo.

Some of these options are discussed in a previous question:

Akka: Send a future message to an Actor

Which one of the three is the preferred way to do it, and why?

Also, I would like to know, if receive should be of type Any => Unit, then why does the code compile when in some cases the partial function of receive returns a Future, not Unit?

Here is a code example of the three options that I mentioned above:

import akka.actor.{Actor, ActorRef, ActorSystem, Props}
import akka.pattern.ask
import akka.util.Timeout
import akka.pattern.pipe

import scala.concurrent.Future
import scala.concurrent.duration._
import scala.language.postfixOps
import scala.util.Success

class ActorIncrement extends Actor {

  def receive = {
    case i: Int =>
      println(s"increment $i")
      sender ! i + 1
  }
}

class ActorEven extends Actor {

  def receive = {
    case i: Int =>
      println(s"$i is even")
  }
}


class ActorOdd extends Actor {

  def receive = {
    case i: Int =>
      println(s"$i is odd")
  }
}

class MyActor(actorIncrement: ActorRef, actorEven: ActorRef, actorOdd: ActorRef) extends Actor {
  import scala.concurrent.ExecutionContext.Implicits.global

  implicit val timeout = Timeout(5 seconds)

  def receive = {
    case i: Int if i % 2 == 0 =>
      println(s"receive a: $i")
      actorIncrement ? i map {
        case j: Int =>
          println(s"$j from increment a")
          actorOdd ! j
      }
    case i: Int =>
      println(s"receive b: $i")
      val future: Future[Any] = actorIncrement ? i
      future onSuccess {
        case i: Int =>
          println(s"$i from increment b")
          actorEven ! i
      }

    case s: String =>
      println(s"receive c: $s")
      (actorIncrement ? s.toInt).mapTo[Int] filter(_ % 2 == 0) andThen { case Success(i: Int) => println(s"$i from increment c") } pipeTo actorEven
  }
}

object TalkToActor extends App {

  // Create the 'talk-to-actor' actor system
  val system = ActorSystem("talk-to-actor")

  val actorIncrement = system.actorOf(Props[ActorIncrement], "actorIncrement")
  val actorEven = system.actorOf(Props[ActorEven], "actorEven")
  val actorOdd = system.actorOf(Props[ActorOdd], "actorOdd")

  val myActor = system.actorOf(Props(new MyActor(actorIncrement, actorEven, actorOdd)), "myActor")

  myActor ! 2
  myActor ! 7
  myActor ! "11"

  Thread.sleep(1000)

  //shutdown system
  system.terminate()
}

回答1:

If you look at how pipeTo is defined in akka.pattern.PipeToSupport,

def pipeTo(recipient: ActorRef)(implicit sender: ActorRef = 
  Actor.noSender): Future[T] = {
    future andThen {
      case Success(r) ⇒ recipient ! r
      case Failure(f) ⇒ recipient ! Status.Failure(f)
    }
  }
}

As you can see... pipeTo is nothing different than just adding andThen call to your Future which either sends the future-result or a Status.Failure message to the piped actor in case your Future fails.

Now the main difference lies in this Status.Failure failure-handling. If you are not using pipeTo, you can handle your failure in whatever way you want to.



标签: scala akka actor