Akka是基于参与者的事件驱动框架,用于构建高度并发,可靠的应用程序。 未来的概念在这样的系统中无处不在,这不足为奇。 通常,您从不阻止等待响应,而是发送消息并期望响应在将来的某个时间到达。 听起来很适合……期货。 此外,Akka的期货之所以特别,有两个原因:Scala语法和类型推断一起大大提高了可读性和单子性。 要充分了解后者的优势,请查看scala.Option备忘单(如果您尚未在Scala中实际掌握monad)。
我们将继续采用另一种方法的Web爬网程序示例 ,这次将Akka置于Scala之上。 首先是基本语法:
val future = Future {
Source.fromURL(
new URL("http://www.example.com"), StandardCharsets.UTF_8.name()
).mkString
那很快! future
是scala.concurrent.Future[String]
推断的类型。 所提供的代码块将在以后异步执行,并且future
( Future[String]
类型的)表示该块的值的句柄。 现在,您应该想知道,如何配置运行此任务的线程? 很好的问题,此代码无法按现状进行编译,需要ExecutionContext
才能进行处理。 ExecutionContext
类似于ExecutorService
但可以隐式给出。 您有几种选择:
import ExecutionContext.Implicits.global
//or
implicit val ec = ExecutionContext.fromExecutorService(Executors.newFixedThreadPool(50))
//or (inside actor)
import context.dispatcher
//or (explicitly)
val future = Future {
//...
} (ec)
第一种方法使用内置的执行上下文,该上下文由尽可能多的CPU /内核组成。 仅在小型应用程序中使用此上下文,因为它无法很好地扩展并且非常不灵活。 第二种方法采用现有的ExecutorService
并将其包装。 您可以完全控制线程的数量及其行为。 注意implicit val
是如何自动拾取的。 如果您在actor内部,则可以重用Akka dispatcher
以使用actor使用的相同线程池来运行任务。 最后,您当然可以显式传递ExecutionContext
。 在下一个示例中,我假设一些隐式上下文可用。
有Future
实例,我们想处理结果。 我什至没有提到同步阻止和等待它们(但如果确实需要,请查看官方文档 )。 ListenableFuture
番石榴的ListenableFuture
精神,我们将首先注册一些完成回调:
Future {
Source.fromURL(new URL("http://www.example.com"), StandardCharsets.UTF_8.name()).mkString
} onComplete {
case Success(html) => logger.info("Result: " + html)
case Failure(ex) => logger.error("Problem", ex)
}
感觉很像ListenableFuture
但语法更ListenableFuture
。 但是,我们的包装盒中还有更强大的工具。 请记住,上次我们有一种同步方法来解析下载HTML,另一种异步方法来计算文档的相关性 (无论如何):
def downloadPage(url: URL) = Future {
Source.fromURL(url, StandardCharsets.UTF_8.name()).mkString
}
def parse(html: String): Document = //...
def calculateRelevance(doc: Document): Future[Double] = //...
当然,我们可以注册onComplete
回调,但是Akka / Scala中的Future是monad,因此我们可以将数据作为一系列链接的强类型转换(为清楚起见而保留的显式类型)进行处理:
val htmlFuture: Future[String] = downloadPage(new URL("http://www.example.com"))
val documentFuture: Future[Document] = htmlFuture map parse
val relevanceFuture: Future[Double] = documentFuture flatMap calculateRelevance
val bigRelevanceFuture: Future[Double] = relevanceFuture filter {_ > 0.5}
bigRelevanceFuture foreach println
我想在这里说清楚。 调用Future.map(someOperation)
不会等待该将来完成。 它只是把它包装并运行someOperation
它完成的那一刻,一段时间的,ekhem,未来。 同样适用于Future.filter
和Future.foreach
。 在这种情况下,您可能会感到惊讶,因为我们通常将此类运算符与集合相关联。 但是,就像Option[T]
, Future[T]
大大简化了一个可能包含也可能不包含一个元素的集合。 通过这种比较,显然上面的代码是做什么的。 Future.filter
调用可能不清楚,但是它基本上表明我们对不满足某些条件的异步操作的结果不感兴趣。 如果谓词产生false
,则永远不会执行foreach
操作。
当然,您可以利用类型推断和链接来获得更简洁的信息,但不一定更容易阅读代码:
downloadPage(new URL("http://www.example.com")).
map(parse).
flatMap(calculateRelevance).
filter(_ > 0.5).
foreach(println
但是最大的胜利来自于悟性。 您可能没有意识到,但是由于Future
实现了map
, foreach
, filter
等(简化),因此我们可以在内部使用它进行理解(与Option[T]
)。 因此,另一种可能是最易读的方法是:
for {
html <- downloadPage(new URL("http://www.example.com"))
relevance <- calculateRelevance(parse(html))
if(relevance > 0.5)
} println(relevance)
println("Done")
感觉非常必要和有序,但是实际上理解的每个步骤都是异步执行的,这里没有阻塞。 "Done"
消息将立即显示,远远早于计算的相关性。 这种构造带来了两全其美的效果–看起来是顺序的,但实际上是在后台运行。 此外,它隐藏了返回值与Future
值的函数( map
与flatMap
)之间的模糊差异。
假设我们在上面的网站列表代码中运行了代码,该列表为我们提供了List[Future[Double]]
,现在我们希望在该List[Future[Double]]
中找到最大的相关性。 现在,您应该拒绝所有涉及阻塞的解决方案。 在Scala中,有两种巧妙的方法可以做到这一点–通过将List[Future[Double]]
为Future[List[Double]]
或折叠期货列表。 第一个解决方案与Guava中的Futures.allAsList
相同:
val futures: Seq[Future[Double]] = //...
val future: Future[Seq[Double]] = Future sequence futures
future.onSuccess{
case x => println(s"Max relevance: ${x.max}")
}
甚至更简洁(请记住,在两种情况下x
都是Seq[Double]
:
Future.sequence(futures).map {x =>
println(s"Max relevance: ${x.max}")
}
请记住,这里没有阻塞。 当上一个基础Future[Double]
报告完成时, Future[Seq[Double]]
完成。 如果您像我一样喜欢foldLeft()
(但不一定在这里),请考虑以下成语:
Future.fold(futures)(0.0) {_ max _} map {maxRel =>
println(s"Max relevance: $maxRel")
}
这些给定的期货一个接一个地迭代,并在给定的期货成功时调用我们提供的{_ max _}
折叠函数。
摘要
Scala和Akka中的期货功能非常强大:它们允许非阻塞,CPU有效的异步编程,但感觉像是命令式单线程编程。 您可以将转换序列应用于单个未来或它们的集合,就像该未来已经解决一样。 在您等待一个阶段,进行一些转换并运行第二阶段的地方,代码看起来绝对必要。 但实际上,一切都是异步的,并且是事件驱动的。 由于Future[V]
单字性质和简洁的语法,Scala中的Future[V]
是一个很好的工具,无需引入过多的仪式。
参考: NoBlogDefFound博客中来自我们JCG合作伙伴 Tomasz Nurkiewicz的Scala中的Akka期货 。
翻译自: https://www.javacodegeeks.com/2013/03/futures-in-akka-with-scala.html