注:下面内容参考自 Scala Akka Document
1.Use With Actors
如果你想要从一个的Actor中得到回应,有两种方式
1.1 send message(actor ! msg)
这种情况只能适用于original sender 是一个Actor
1.2通过Future
例子:
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
//下面这行代码会让线程阻塞,等待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源码可以了解
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方法
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
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
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!"))
}