Scala Akka Futures + Actor(续)

注:下面内容参考自 Scala Akka Document

1.Use With Actors

如果你想要从一个的Actor中得到回应,有两种方式

1.1 send message(actor ! msg)  

这种情况只能适用于original sender 是一个Actor

1.2通过Future

例子:

import akka.actor.{Actor, ActorSystem, Props}
import akka.util.Timeout
import scala.concurrent.duration._
import scala.language.postfixOps
import scala.concurrent.{Await,Future}
import akka.pattern.ask

case object AskNameMessage
class TestActor(Name:String) extends Actor{
  val name = Name
  def receive={
    case AskNameMessage => sender ! name
    case _ => println("sorry,I can not answer your question")
  }
}
object AskTest extends App{
  val system = ActorSystem("AskTestSystem")
  val myActor  = system.actorOf(Props(new TestActor("Rxy")),name="myActor")

  implicit val timeout = Timeout(5 seconds)  //隐式设置timeout变量(可以参见Ask方法的源码)

//如果不设置这个变量,执行会出现"can not find implicit value for parameter timeout"错误

//如果在该时间内没有返回消息,会出现 AskTImeoutException

  val future = myActor ? AskNameMessage //使用Actor's 的?方法发送消息并返回一个Future。
//下面这行代码会让线程阻塞,等待Actor返回Future   (返回的Future是Future[Any],为什么要asInstanceOf)
  val result = Await.result(future,timeout.duration).asInstanceOf[String]
  println(result)
//下面的代码不使用阻塞的方式。mapTo将会返回新的Future。如果成功Future中应该包含着结果,否则返回ClassCastException

  val future2:Future[String] = ask(myActor,AskNameMessage).mapTo[String]

//mapTo方法使用之后就不需要asInstanceOf,在后面的mapTo源码可以了解

  val result2 = Await.result(future2,1 second)
  println(result2)
  system.shutdown()

}

上述代码中两个result都是Rxy


1.3 ask方法

def ask(actorRef: ActorRef, message: Any)(implicit timeout: Timeout): Future[Any] = actorRef ? message

从源码可以看出ask方法接受一个ActorRef类型的变量(比如说用ActorSystem.actorOf产生的变量)和一个message,message可以是任何类型的,包括样类。参数后面的Timeout类型的变量必须隐士的定义,这个变量的意思是如果在规定的时间里面没有返回,就会报AskTImeoutException 。ask方法最终返回Future[Any]类型的变量,后面可以用asInstanceof或者是mapTo来确定类型

1.4 mapTo方法

源码如下:
def mapTo[S](implicit tag: ClassTag[S]): Future[S] = {
    implicit val ec = internalExecutor
    val boxedClass = {
      val c = tag.runtimeClass
      if (c.isPrimitive) Future.toBoxed(c) else c
    }
    require(boxedClass ne null)
    map(s => boxedClass.cast(s).asInstanceOf[S])
  }


2. Use Directly

在Akka中Future更平常的并发方式不再需要Actor。

如果产生一个Actor pool的主要原因只是为了并行计算,这里有更简单更快速的方式

   import akka.dispatch.Await
    import akka.dispatch.Future
    import akka.util.duration._
     
    val future = Future {
      "Hello" + "World"
    }
    val result = Await.result(future, 1 second)

Future中的代码块将会被默认的Dispatcher执行,返回result,Future完成。result是一个String :”Hello World“,这不像Future来源于Actor,这节省了管理Actor的开销

你可以创建一个already completed的Future   :    使用Promise companion

val future = Promise.successful("Yay!")

或者失败的

val otherFuture = Promise.failed[String](new IllegalArgumentException("Bang!"))

3. Functional Futures

Akka's Future提供了类似与Scala's collection的方法,实现用"pipelines"或者是"streams"方式实现结果遍历

3.1 map

map方法执行函数,对结果进行修改返回一个新的结果

例子1:

 val f1 = Future{
    "Hello" + "World"
  }
  val f2 = f1 map{
    x => x.length
  }
  val result  = Await.result(f2,1 second)
  println(result)

上述代码建立了第二个Future,上面保留了Int。当f1完成会立即执行f2。

3.2 For Comprehensions

 val f1 = Future {
    ("Hello" + "World").length
  }
  val f2 = Future{
    3
  }
  val f3 = for{
    a <- f1
    b <- f2
  }yield a * b
  val result = Await.result(f3, 1 second)
  println(result)

输出结果:30

3.3 Composing Futures

下面的例子就是将future进行组合

import akka.actor.{Actor, ActorSystem, Props}
import scala.concurrent.{Await, Future, future}
import akka.pattern.ask
import akka.util.Timeout
import scala.concurrent.duration._
case class PersonA(num:Int)
class Actor1 extends Actor{
  def receive={
    case PersonA(num) => sender!num
    case _ => throw new Exception("Message Exception")
  }
}
//Actor3,Actor2和Actor1完全一样,只是类名字不一样

object FutureTest extends App {
  implicit  val timeout = Timeout(5 second)
  val system = ActorSystem("FutureTest")
  val actor1 = system.actorOf(Props[Actor1],name = "actor1")
  val actor2 = system.actorOf(Props[Actor2],name = "actor2")
  val actor3 = system.actorOf(Props[Actor3],name = "sactor3")
  val f1 = ask(actor1,PersonA(10)).mapTo[Int]
  val f2 = ask(actor2,PersonA(20)).mapTo[Int]
  val a = Await.result(f1, 1 second)

  val b = Await.result(f2, 1 second)

//在向actor3发送消息之前会等待actor1和actor2的结果

  val f3 = ask(actor3,PersonA(a+b))
  val result = Await.result(f3,1 second).asInstanceOf[Int]
  println(result)
}

//输出结果:30

当需要处理很多数量的actor时,就不好驾驭,这时sequence和traverse可以用来处理这种情况

sequence

例子2:

import akka.actor.{Actor, ActorSystem, Props}
import scala.concurrent.{Await, Future, future}
import akka.pattern.ask
import akka.util.Timeout
import scala.concurrent.duration._
import scala.concurrent.ExecutionContext.Implicits.global
case class PersonA(num:Int)
class Actor1 extends Actor{
  def receive={
    case PersonA(num) => sender!num
    case _ => throw new Exception("Message Exception")
  }
}
object FutureTest extends App {
  implicit  val timeout = Timeout(5 second)
  val system = ActorSystem("FutureList")
  val actor1 = system.actorOf(Props[Actor1],name= "actor1")
  val listOfFutures = List.fill(100)(ask(actor1,PersonA(10)).mapTo[Int])
  val futureList = Future.sequence(listOfFutures)
  val sum = Await.result(futureList.map(_.sum),1 second)
  println(sum)

}

输出结果:1000

Future.sequence接受List[Future[Int]]并将其转换成Future[List[Int]],然后我们可以使用map方法对List[Int]进行操作

补充:

sum:

def sum[B >: A](implicit num: Numeric[B]): B = foldLeft(num.zero)(num.plus)

fill:

def fill[A](n: Int)(elem: => A): CC[A] = {
  val b = newBuilder[A]
  b.sizeHint(n)
  var i = 0
  while (i < n) {
    b += elem
    i += 1
  }
  b.result()
}

sequence:

def sequence[A, M[X] <: TraversableOnce[X]](in: M[Future[A]])(implicit cbf: CanBuildFrom[M[Future[A]], A, M[A]], executor: ExecutionContext): Future[M[A]] = {
  in.foldLeft(successful(cbf(in))) {
    (fr, fa) => for (r <- fr; a <- fa) yield (r += a)
  } map (_.result())
}

traverse

traverse和sequence类似,但是接受的参数是不同的。

traverse接受参数T[A]和函数A=>Future[B],返回Future[T[B]]

例子:

val futureList = Future.traverse((1 to 100).toList)(x => Future(x * 2 -1))
val sum = Await.result(futureList.map(_.sum),1 second)
println(sum)

上述代码可以转换成下面的形式

val futureList = Future.sequence((1 to 100).toList.map(x ⇒ Future(x * 2 - 1)))
val oddSum = Await.result(futureList.map(_.sum), 1 second)

但是使用traverse更加快速,因为不需要产生中间值List[Future[Int]]

fold

fold方法接受一个start-value,Future的一个sequence,一个函数

 val futures = for(i <- 1 to 1000)yield Future(i * 2)  //创建Future的一个sequence

  val futureSum =Future.fold(futures)(0)(_+_)  当Future创建完毕, 函数应用到future序列中的所有元素//异步执行

  val sum = Await.result(futureSum,1 second)

  println(sum)    //1001000

def fold[T, R](futures: TraversableOnce[Future[T]])(zero: R)(@deprecatedName('foldFun) op: (R, T) => R)(implicit executor: ExecutionContext): Future[R] = {
  if (futures.isEmpty) successful(zero)
  else sequence(futures).map(_.foldLeft(zero)(op))
}

reduce

对应与上面的fold,如果没有初始值,可以使用reduce

 val futures = for (i ← 1 to 1000) yield Future(i * 2) // Create a sequence of Futures
  val futureSum = Future.reduce(futures)(_ + _)  当Future创建完毕,函数应用到future序列中的所有元素//异步执行
  val sum = Await.result(futureSum, 1 second)

  println(sum)    //输出的结果和fold一样

def reduce[T, R >: T](futures: TraversableOnce[Future[T]])(op: (R, T) => R)(implicit executor: ExecutionContext): Future[R] = {
  if (futures.isEmpty) failed(new NoSuchElementException("reduce attempted on empty collection"))
  else sequence(futures).map(_ reduceLeft op)
}

Define Ordering

callback执行潜在是并行的方式,没有固定的顺序,当你需要制定操作序列的时候可能会变得很棘手。

有一种解决方式称为andThen。它会产生特殊的future,使用特殊的callback方法。返回的result和普通的future是一样的

例子:
    val result = Future { loadPage(url) } andThen {
      case Left(exception) ⇒ log(exception)
    } andThen {
      case _ ⇒ watchSomeTV
    }

Exception

既然Future处理结果的方式是并还行处理的,则Exception也需要用不同的方式进行处理

我们希望的是Future能够正常计算,返回包含有效值的结果,但是如果出现异常,那么结果肯定就是无效的。这时使用Await.result会抛出异常,正确处理。但是如果你要求不抛出,使用特定值代替,那么就可以使用recover方法。

例子:

    val future = akka.pattern.ask(actor, msg1) recover {
      case e: ArithmeticException ⇒ 0
    }

在上述代码,如果actor回应的akka.actor.Status.Failure包含ArithmeticException,则Future就会有0

recover非常类似与try catch

我们可以使用flatMap来处理多组

    val future = akka.pattern.ask(actor, msg1) recoverWith {
      case e: ArithmeticException        ⇒ Promise.successful(0)
      case foo: IllegalArgumentException ⇒ Promise.failed[Int](new IllegalStateException("All br0ken!"))
    }



  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值