Future与Promise

https://code.csdn.NET/DOC_Scala/chinese_scala_offical_document/file/Futures-and-Promises-cn.md#anchor_0html

 

Philipp Haller, Aleksandar Prokopec, Heather Miller, Viktor Klang, Roland Kuhn, and Vojin Jovanovic著java

简介

Future提供了一套高效便捷的非阻塞并行操做管理方案。其基本思想很简单,所谓Future,指的是一类占位符对象,用于指代某些还没有完成的计算的结果。通常来讲,由Future指代的计算都是并行执行的,计算完毕后可另行获取相关计算结果。以这种方式组织并行任务,即可以写出高效、异步、非阻塞的并行代码。数组

默认状况下,future和promise并不采用通常的阻塞操做,而是依赖回调进行非阻塞操做。为了在语法和概念层面更加简明扼要地使用这些回调,Scala还提供了flatMap、foreach和filter等算子,使得咱们可以以非阻塞的方式对future进行组合。固然,future仍然支持阻塞操做——必要时,能够阻塞等待future(不过并不鼓励这样作)。promise

Future

所谓Future,是一种用于指代某个还没有就绪的值的对象。而这个值,每每是某个计算过程的结果:服务器

  • 若该计算过程还没有完成,咱们就说该Future未就位;
  • 若该计算过程正常结束,或中途抛出异常,咱们就说该Future已就位。

Future的就位分为两种状况:网络

  • 当Future带着某个值就位时,咱们就说该Future携带计算结果成功就位。
  • 当Future因对应计算过程抛出异常而就绪,咱们就说这个Future因该异常而失败。

Future的一个重要属性在于它只能被赋值一次。一旦给定了某个值或某个异常,future对象就变成了不可变对象——没法再被改写。session

建立future对象最简单的方法是调用future方法,该future方法启用异步(asynchronous)计算并返回保存有计算结果的futrue,一旦该future对象计算完成,其结果就变的可用。闭包

注意_Future[T]_ 是表示future对象的类型,而future是方法,该方法建立和调度一个异步计算,并返回随着计算结果而完成的future对象。并发

这最好经过一个例子予以说明。app

假设咱们使用某些流行的社交网络的假定API获取某个用户的朋友列表,咱们将打开一个新对话(session),而后发送一个请求来获取某个特定用户的好友列表。

import scala.concurrent._ import ExecutionContext.Implicits.global val session = socialNetwork.createSessionFor("user", credentials) val session = socialNetwork.createSessionFor("user", credentials) session.getFriends() } 

以上,首先导入scala.concurrent 包使得Future类型和future构造函数可见。咱们将立刻解释第二个导入。

而后咱们初始化一个session变量来用做向服务器发送请求,用一个假想的 createSessionFor 方法来返回一个List[Friend]。为了得到朋友列表,咱们必须经过网络发送一个请求,这个请求可能耗时很长。这能从调用getFriends方法获得解释。为了更好的利用CPU,响应到达前不该该阻塞(block)程序的其余部分执行,因而在计算中使用异步。future方法就是这样作的,它并行地执行指定的计算块,在这个例子中是向服务器发送请求和等待响应。

一旦服务器响应,future f 中的好友列表将变得可用。

未成功的尝试可能会致使一个异常(exception)。在下面的例子中,session的值未被正确的初始化,因而在future的计算中将抛出NullPointerException,future f 不会圆满完成,而是以此异常失败。

val session = null val session = socialNetwork.createSessionFor("user", credentials) session.getFriends } 

import ExecutionContext.Implicits.global 上面的线条导入默认的全局执行上下文(global execution context),执行上下文执行执行提交给他们的任务,也可把执行上下文看做线程池,这对于future方法来讲是必不可少的,由于这能够处理异步计算如何及什么时候被执行。咱们能够定义本身的执行上下文,并在future上使用它,可是如今只须要知道你可以经过上面的语句导入默认执行上下文就足够了。

咱们的例子是基于一个假定的社交网络API,此API的计算包含发送网络请求和等待响应。提供一个涉及到你能试着当即使用的异步计算的例子是公平的。假设你有一个文本文件,你想找出一个特定的关键字第一次出现的位置。当磁盘正在检索此文件内容时,这种计算可能会陷入阻塞,所以并行的执行该操做和程序的其余部分是合理的(make sense)。

val firstOccurrence: Future[Int] = future {
  val source = scala.io.Source.fromFile("myText.txt")
  source.toSeq.indexOfSlice("myKeyword")
}

Callbacks(回调函数)

如今咱们知道如何开始一个异步计算来建立一个新的future值,可是咱们没有展现一旦此结果变得可用后如何来使用,以便咱们可以用它来作一些有用的事。咱们常常对计算结果感兴趣而不只仅是它的反作用。

在许多future的实现中,一旦future的client对future的结果感兴趣,它不得不阻塞它本身的计算直到future完成——而后才能使用future的值继续它本身的计算。虽然这在Scala的Future API(在后面会展现)中是容许的,可是从性能的角度来看更好的办法是一种彻底非阻塞的方法,即在future中注册一个回调,future完成后这个回调称为异步回调。若是当注册回调时future已经完成,则回调多是异步执行的,或在相同的线程中循序执行。

注册回调最一般的形式是使用OnComplete方法,即建立一个Try[T] => U类型的回调函数。若是future成功完成,回调则会应用到Success[T]类型的值中,不然应用到Failure[T]类型的值中。

Try[T] 和Option[T]或 Either[T, S]类似,由于它是一个可能持有某种类型值的单子。然而,它是特地设计来保持一个值或某个可抛出(throwable)对象。Option[T] 既能够是一个值(如:Some[T])也能够是彻底无值(如:None),若是Try[T]得到一个值则它为Success[T] ,不然为Failure[T]的异常。 Failure[T] 得到更多的关于为何这儿没值的信息,而不只仅是None。同时也能够把Try[T]看做一种特殊版本的Either[Throwable, T],专门用于左值为可抛出类型(Throwable)的情形。

回到咱们的社交网络的例子,假设咱们想要获取咱们最近的帖子并显示在屏幕上,咱们经过调用getRecentPosts方法得到一个返回值List[String]——一个近期帖子的列表文本:

val f: Future[List[String]] = future { session.getRecentPosts } f onComplete { case Success(posts) => for (post <- posts) println(post) case Success(posts) => for (post <- posts) println(post) } 

onComplete方法通常在某种意义上它容许客户处理future计算出的成功或失败的结果。对于仅仅处理成功的结果,onSuccess 回调使用以下(该回调以一个偏函数(partial function)为参数):

val f: Future[List[String]] = future { session.getRecentPosts } f onSuccess { case posts => for (post <- posts) println(post) } 

对于处理失败结果,onFailure回调使用以下:

val f: Future[List[String]] = future { session.getRecentPosts } f onFailure { case t => println("An error has occured: " + t.getMessage) } f onSuccess { case posts => for (post <- posts) println(post) } 

若是future失败,即future抛出异常,则执行onFailure回调。

由于偏函数具备 isDefinedAt方法, onFailure方法只有在特定的Throwable类型对象中被定义才会触发。下面例子中的onFailure回调永远不会被触发:

val f = future { 2 / 0 } f onFailure { case npe: NullPointerException => println("I'd be amazed if this printed out.") } 

回到前面查找某个关键字第一次出现的例子,咱们想要在屏幕上打印出此关键字的位置:

val firstOccurrence: Future[Int] = future {
  val source = scala.io.Source.fromFile("myText.txt")
  source.toSeq.indexOfSlice("myKeyword")
}

firstOccurrence onSuccess {
  case idx => println("The keyword first appears at position: " + idx)
}

firstOccurrence onFailure {
  case t => println("Could not process file: " + t.getMessage)
}

onComplete,、onSuccess 和 onFailure 方法都具备Unit的结果类型,这意味着不能连接使用这些方法的回调。注意这种设计是为了不暗示而刻意为之的,由于连接回调也许暗示着按照必定的顺序执行注册回调(回调注册在同一个future中是无序的)。

也就是说,咱们如今应讨论论什么时候调用callback。由于callback须要future的值是可用的,全部回调只能在future完成以后被调用。然而,不能保证callback在完成future的线程或建立callback的线程中被调用。反而, 回调(callback)会在future对象完成以后的一些线程和一段时间内执行。因此咱们说回调(callback)最终会被执行。

此外,回调(callback)执行的顺序不是预先定义的,甚至在相同的应用程序中callback的执行顺序也不尽相同。事实上,callback也许不是一个接一个连续的调用,可是可能会在同一时间同时执行。这意味着在下面的例子中,变量totalA也许不能在计算上下文中被设置为正确的大写或者小写字母。

@volatile var totalA = 0 val text = future { "na" * 16 + "BATMAN!!!" } text onSuccess { case txt => totalA += txt.count(_ == 'a') } text onSuccess { case txt => totalA += txt.count(_ == 'a') } 

以上,这两个回调(callbacks)多是一个接一个地执行的,这样变量totalA获得的预期值为18。然而,它们也多是并发执行的,因而totalA最终多是16或2,由于+= 是一个不可分割的操做符(即它是由一个读和一个写的步骤组成,这样就可能使其与其余的读和写任意交错执行)。

考虑到完整性,回调的使用情景列在这儿:

  • 在future中注册onComplete回调的时候要确保最后future执行完成以后调用相应的终止回调。

  • 注册onSuccess或者onFailure回调时也和注册onComplete同样,不一样之处在于future执行成功或失败分别调用onSuccess或onSuccess的对应的闭包。

  • 注册一个已经完成的future的回调最后将致使此回调一直处于执行状态(1所隐含的)。

  • 在future中注册多个回调的状况下,这些回调的执行顺序是不肯定的。事实上,这些回调也许是同时执行的,然而,特定的ExecutionContext执行可能致使明确的顺序。

  • 在一些回调抛出异常的状况下,其余的回调的执行不受影响。

  • 在一些状况下,回调函数永远不能结束(例如,这些回调处于无限循环中),其余回调可能彻底不会执行。在这种状况下,对于那些潜在的阻塞回调要使用阻塞的构造(例子以下)。

  • 一旦执行完,回调将从future对象中移除,这样更适合JVM的垃圾回收机制(GC)。

函数组合(Functional Composition)和For解构(For-Comprehensions)

尽管前文所展现的回调机制已经足够把future的结果和后继计算结合起来的,可是有些时候回调机制并不易于使用,且容易形成冗余的代码。咱们能够经过一个例子来讲明。假设咱们有一个用于进行货币交易服务的API,咱们想要在有盈利的时候购进一些美圆。让咱们先来看看怎样用回调来解决这个问题:

val rateQuote = future { connection.getCurrentValue(USD) } rateQuote onSuccess { case quote => val purchase = future { if (isProfitable(quote)) connection.buy(amount, quote) else throw new Exception("not profitable") } purchase onSuccess { case _ => println("Purchased " + amount + " USD") } } 

首先,咱们建立一个名为rateQuote的future对象并得到当前的汇率。在服务器返回了汇率且该future对象成功完成了以后,计算操做才会从onSuccess回调中执行,这时咱们就能够开始判断买仍是不买了。因此咱们建立了另外一个名为purchase的future对象,用来在可盈利的状况下作出购买决定,并在稍后发送一个请求。最后,一旦purchase运行结束,咱们会在标准输出中打印一条通知消息。

这确实是可行的,可是有两点缘由使这种作法并不方便。其一,咱们不得不使用onSuccess,且不得不在其中嵌套purchase future对象。试想一下,若是在purchase执行完成以后咱们可能会想要卖掉一些其余的货币。这时咱们将不得不在onSuccess的回调中重复这个模式,从而可能使代码过分嵌套,过于冗长,而且难以理解。

其二,purchase只是定义在局部范围内--它只能被来自onSuccess内部的回调响应。这也就是说,这个应用的其余部分看不到purchase,并且不能为它注册其余的onSuccess回调,好比说卖掉些别的货币。

为解决上述的两个问题,futures提供了组合器(combinators)来使之具备更多易用的组合形式。映射(map)是最基本的组合器之一。试想给定一个future对象和一个经过映射来得到该future值的函数,映射方法将建立一个新Future对象,一旦原来的Future成功完成了计算操做,新的Future会经过该返回值来完成本身的计算。你可以像理解容器(collections)的map同样来理解future的map。

让咱们用map的方法来重构一下前面的例子:

val rateQuote = future { connection.getCurrentValue(USD) } val purchase = rateQuote map { quote => if (isProfitable(quote)) connection.buy(amount, quote) else throw new Exception("not profitable") } purchase onSuccess { case _ => println("Purchased " + amount + " USD") } 

经过对rateQuote的映射咱们减小了一次onSuccess的回调,更重要的是避免了嵌套。这时若是咱们决定出售一些货币就能够再次使用purchase方法上的映射了。

但是若是isProfitable方法返回了false将会发生些什么?会引起异常?这种状况下,purchase的确会由于异常而失败。不只仅如此,想象一下,连接的中断和getCurrentValue方法抛出异常会使rateQuote的操做失败。在这些状况下映射将不会返回任何值,而purchase也会会自动的以和rateQuote相同的异常而执行失败。

总之,若是原Future的计算成功完成了,那么返回的Future将会使用原Future的映射值来完成计算。若是映射函数抛出了异常则Future也会带着该异常完成计算。若是原Future因为异常而计算失败,那么返回的Future也会包含相同的异常。这种异常的传导方式也一样适用于其余的组合器(combinators)。

使之可以在For-comprehensions原则下使用,是设计Future的目的之一。也正是由于这个缘由,Future还拥有flatMap,filter和foreach等组合器。其中flatMap方法能够构造一个函数,它能够把值映射到一个姑且称为g的新future,而后返回一个随g的完成而完成的Future对象。

让咱们假设咱们想把一些美圆兑换成瑞士法郎。咱们必须为这两种货币报价,而后再在这两个报价的基础上肯定交易。下面是一个在for-comprehensions中使用flatMap和withFilter的例子:

val usdQuote = future { connection.getCurrentValue(USD) } val chfQuote = future { connection.getCurrentValue(CHF) } val purchase = for { usd <- usdQuote chf <- chfQuote if isProfitable(usd, chf) } yield connection.buy(amount, chf) purchase onSuccess { case _ => println("Purchased " + amount + " CHF") } 

purchase只有当usdQuote和chfQuote都完成计算之后才能完成-- 它以其余两个Future的计算值为前提因此它本身的计算不能更早的开始。

上面的for-comprhension将被转换为:

val purchase = usdQuote flatMap { usd => chfQuote .withFilter(chf => isProfitable(usd, chf)) .map(chf => connection.buy(amount, chf)) } 

这的确是比for-comprehension稍微难以把握一些,可是咱们这样分析有助于您更容易的理解flatMap的操做。FlatMap操做会把自身的值映射到其余future对象上,并随着该对象计算完成的返回值一块儿完成计算。在咱们的例子里,flatMap用usdQuote的值把chfQuote的值映射到第三个futrue对象里,该对象用于发送必定量瑞士法郎的购入请求。只有当经过映射返回的第三个future对象完成了计算,purchase才能完成计算。

这可能有些难以置信,但幸运的是faltMap操做在for-comprhensions模式之外不多使用,由于for-comprehensions自己更容易理解和使用。

再说说filter,它能够用于建立一个新的future对象,该对象只有在知足某些特定条件的前提下才会获得原始future的计算值,不然就会抛出一个NoSuchElementException的异常而失败。调用了filter的future,其效果与直接调用withFilter彻底同样。

做为组合器的collect同filter之间的关系有些相似容器(collections)API里的那些方法之间的关系。

值得注意的是,调用foreach组合器并不会在计算值可用的时候阻塞当前的进程去获取计算值。偏偏相反,只有当future对象成功计算完成了,foreach所迭代的函数才可以被异步的执行。这意味着foreach与onSuccess回调意义彻底相同。

因为Future trait(译注: trait有点相似Java中的接口(interface)的概念)从概念上看包含两种类型的返回值(计算结果和异常),因此组合器会有一个处理异常的需求。

比方说咱们准备在rateQuote的基础上决定购入必定量的货币,那么connection.buy方法须要知道购入的数量和指望的报价值,最终完成购买的数量将会被返回。假如报价值恰恰在这个节骨眼儿改变了,那buy方法将会抛出一个QuoteChangedExecption,而且不会作任何交易。若是咱们想让咱们的Future对象返回0而不是抛出那个该死的异常,那咱们须要使用recover组合器:

val purchase: Future[Int] = rateQuote map {
  quote => connection.buy(amount, quote)
} recover {
  case QuoteChangedException() => 0
}

这里用到的recover可以建立一个新future对象,该对象当计算完成时持有和原future对象同样的值。若是执行不成功则偏函数的参数会被传递给使原Future失败的那个Throwable异常。若是它把Throwable映射到了某个值,那么新的Future就会成功完成并返回该值。若是偏函数没有定义在Throwable中,那么最终产生结果的future也会失败并返回一样的Throwable。

组合器recoverWith可以建立一个新future对象,当原future对象成功完成计算时,新future对象包含有和原future对象相同的计算结果。若原future失败或异常,偏函数将会返回形成原future失败的相同的Throwable异常。若是此时Throwable又被映射给了别的future,那么新Future就会完成并返回这个future的结果。recoverWith同recover的关系跟flatMap和map之间的关系很像。

fallbackTo组合器生成的future对象能够在该原future成功完成计算时返回结果,若是原future失败或异常返回future参数对象的成功值。在原future和参数future都失败的状况下,新future对象会完成并返回原future对象抛出的异常。正以下面的例子中,本想打印美圆的汇率,可是在获取美圆汇率失败的状况下会打印出瑞士法郎的汇率:

val usdQuote = future { connection.getCurrentValue(USD) } map { usd => "Value: " + usd + "$" } val chfQuote = future { connection.getCurrentValue(CHF) } map { chf => "Value: " + chf + "CHF" } al anyQuote = usdQuote fallbackTo chfQuote anyQuote onSuccess { println(_) } 

组合器andThen的用法是出于纯粹的side-effecting目的。经andThen返回的新Future不管原Future成功或失败都会返回与原Future如出一辙的结果。一旦原Future完成并返回结果,andThen后跟的代码块就会被调用,且新Future将返回与原Future同样的结果,这确保了多个andThen调用的顺序执行。正以下例所示,这段代码能够从社交网站上把近期发出的帖子收集到一个可变集合里,而后把它们都打印在屏幕上:

val allposts = mutable.Set[String]() future { session.getRecentPosts } andThen { posts => allposts ++= posts } andThen { posts => clearAll() for (post <- allposts) render(post) } 

综上所述,Future的组合器功能是纯函数式的,每种组合器都会返回一个与原Future相关的新Future对象。

投影(Projections)

为了确保for解构(for-comprehensions)可以返回异常,futures也提供了投影(projections)。若是原future对象失败了,失败的投影(projection)会返回一个带有Throwable类型返回值的future对象。若是原Future成功了,失败的投影(projection)会抛出一个NoSuchElementException异常。下面就是一个在屏幕上打印出异常的例子:

val f = future { 2 / 0 } for (exc <- f.failed) println(exc) 

下面的例子不会在屏幕上打印出任何东西:

val f = future { 4 / 2 } for (exc <- f.failed) println(exc) 

Future的扩展

用更多的实用方法来对Futures API进行扩展支持已经被提上了日程,这将为不少外部框架提供更多专业工具。

Blocking

正如前面所说的,在future的blocking很是有效地缓解性能和预防死锁。虽然在futures中使用这些功能方面的首选方式是Callbacks和combinators,但在某些处理中也会须要用到blocking,而且它也是被Futures and Promises API所支持的。

在以前的并发交易(concurrency trading)例子中,在应用的最后有一处用到block来肯定是否全部的futures已经完成。这有个如何使用block来处理一个future结果的例子:

import scala.concurrent._ import scala.concurrent.duration._ def main(args: Array[String]) { val rateQuote = future { connection.getCurrentValue(USD) } val purchase = rateQuote map { quote => if (isProfitable(quote)) connection.buy(amount, quote) else throw new Exception("not profitable") } Await.result(purchase, 0 nanos) } 

在这种状况下这个future是不成功的,这个调用者转发出了该future对象不成功的异常。它包含了失败的投影(projection)-- 阻塞(blocking)该结果将会形成一个NoSuchElementException异常在原future对象被成功计算的状况下被抛出。

相反的,调用Await.ready来等待这个future直到它已完成,但获不到它的结果。一样的方式,调用那个方法时若是这个future是失败的,它将不会抛出异常。

The Future trait实现了Awaitable trait还有其ready()result()方法。这些方法不能被客户端直接调用,它们只能经过执行环境上下文来进行调用。

为了容许程序调用多是阻塞式的第三方代码,而又没必要实现Awaitable特质,原函数能够用以下的方式来调用:

blocking { potentiallyBlockingCall() } 

这段blocking代码也能够抛出一个异常。在这种状况下,这个异常会转发给调用者。

异常(Exceptions)

当异步计算抛出未处理的异常时,与那些计算相关的futures就失败了。失败的futures存储了一个Throwable的实例,而不是返回值。Futures提供onFailure回调方法,它用一个PartialFunction去表示一个Throwable。下列特殊异常的处理方式不一样:

scala.runtime.NonLocalReturnControl[_] --此异常保存了一个与返回相关联的值。一般状况下,在方法体中的返回结构被调用去抛出这个异常。相关联的值将会存储到future或一个promise中,而不是一直保存在这个异常中。

ExecutionException-当由于一个未处理的中断异常、错误或者scala.util.control.ControlThrowable致使计算失败时会被存储起来。这种状况下,ExecutionException会为此具备未处理的异常。这些异常会在执行失败的异步计算线程中从新抛出。这样作的目的,是为了防止正常状况下没有被客户端代码处理过的那些关键的、与控制流相关的异常继续传播下去,同时告知客户端其中的future对象是计算失败的。

更精确的语义描述请参见 [NonFatal]。

Promises

到目前为止,咱们仅考虑了经过异步计算的方式建立future对象来使用future的方法。尽管如此,futures也可使用promises来建立。

若是说futures是为了一个尚未存在的结果,而当成一种只读占位符的对象类型去建立,那么promise就被认为是一个可写的,能够实现一个future的单一赋值容器。这就是说,promise经过这种success方法能够成功去实现一个带有值的future。相反的,由于一个失败的promise经过failure方法就会实现一个带有异常的future。

一个promise p经过p.future方式返回future。 这个futrue对象被指定到promise p。根据这种实现方式,可能就会出现p.future与p相同的状况。

考虑下面的生产者 - 消费者的例子,其中一个计算产生一个值,并把它转移到另外一个使用该值的计算。这个传递中的值经过一个promise来完成。

import scala.concurrent.{ future, promise } import scala.concurrent.ExecutionContext.Implicits.global val p = promise[T] val f = p.future val producer = future { val r = produceSomething() p success r continueDoingSomethingUnrelated() } val consumer = future { startDoingSomething() f onSuccess { case r => doSomethingWithResult() } } 

在这里,咱们建立了一个promise并利用它的future方法得到由它实现的Future。而后,咱们开始了两种异步计算。第一种作了某些计算,结果值存放在r中,经过执行promise p,这个值被用来完成future对象f。第二种作了某些计算,而后读取实现了future f的计算结果值r。须要注意的是,在生产者完成执行continueDoingSomethingUnrelated() 方法这个任务以前,消费者能够得到这个结果值。

正如前面提到的,promises具备单赋值语义。所以,它们仅能被实现一次。在一个已经计算完成的promise或者failed的promise上调用success方法将会抛出一个IllegalStateException异常。

下面的这个例子显示了如何fail a promise。

val p = promise[T] val f = p.future val producer = future { val r = someComputation if (isInvalid(r)) p failure (new IllegalStateException) else { val q = doSomeMoreComputation(r) p success q } } 

如上,生产者计算出一个中间结果值r,并判断它的有效性。若是它不是有效的,它会经过返回一个异常实现promise p的方式fails the promise,关联的future f是failed。不然,生产者会继续它的计算,最终使用一个有效的结果值实现future f,同时实现 promise p。

Promises也能经过一个complete方法来实现,这个方法采用了一个potential value Try[T],这个值要么是一个类型为Failure[Throwable]的失败的结果值,要么是一个类型为Success[T]的成功的结果值。

相似success方法,在一个已经完成(completed)的promise对象上调用failure方法和complete方法一样会抛出一个IllegalStateException异常。

应用前面所述的promises和futures方法的一个优势是,这些方法是单一操做的而且是没有反作用(side-effects)的,所以程序是具备肯定性的(deterministic)。肯定性意味着,若是该程序没有抛出异常(future的计算值被得到),不管并行的程序如何调度,那么程序的结果将会永远是同样的。

在一些状况下,客户端也许但愿可以只在promise没有完成的状况下完成该promise的计算(例如,若是有多个HTTP请求被多个不一样的futures对象来执行,而且客户端只关心地一个HTTP应答(response),该应答对应于地一个完成该promise的future)。由于这个缘由,future提供了tryComplete,trySuccess和tryFailure方法。客户端须要意识到调用这些的结果是不肯定的,调用的结果将以来从程序执行的调度。

completeWith方法将用另一个future完成promise计算。当该future结束的时候,该promise对象获得那个future对象一样的值,以下的程序将打印1:

val f = future { 1 } val p = promise[Int] p completeWith f p.future onSuccess { case x => println(x) } 

当让一个promise以异常失败的时候,三总子类型的Throwable异常被分别的处理。若是中断该promise的可抛出(Throwable)一场是scala.runtime.NonLocalReturnControl,那么该promise将以对应的值结束;若是是一个Error的实例,InterruptedException或者scala.util.control.ControlThrowable,那么该可抛出(Throwable)异常将会封装一个ExecutionException异常,该ExectionException将会让该promise以失败结束。

经过使用promises,futures的onComplete方法和future的构造方法,你可以实现前文描述的任何函数式组合组合器(compition combinators)。让咱们来假设一下你想实现一个新的组合起,该组合器首先使用两个future对象f和,产生第三个future,该future可以用f或者g来完成,可是只在它可以成功完成的状况下。

这里有个关于如何去作的实例:

def first[T](f: Future[T], g: Future[T]): Future[T] = { val p = promise[T] f onSuccess { case x => p.trySuccess(x) } g onSuccess { case x => p.trySuccess(x) } p.future } 

注意,在这种实现方式中,若是f与g都不是成功的,那么first(f, g)将不会实现(即返回一个值或者返回一个异常)。

工具(Utilities)

为了简化在并发应用中处理时序(time)的问题,scala.concurrent引入了Duration抽象。Duration不是被做为另一个一般的时间抽象存在的。他是为了用在并发(concurrency)库中使用的,Duration位于scala.concurrent包中。

Duration是表示时间长短的基础类,其能够是有限的或者无限的。有限的duration用FiniteDuration类来表示,并经过时间长度(length)java.util.concurrent.TimeUnit来构造。无限的durations,一样扩展了Duration,只在两种状况下存在,Duration.InfDuration.MinusInf。库中一样提供了一些Durations的子类用来作隐式的转换,这些子类不该被直接使用。

抽象的Duration类包含了以下方法:

到不一样时间单位的转换(toNanos, toMicros, toMillis, toSeconds, toMinutes, toHours, toDays and toUnit(unit: TimeUnit))。 durations的比较(<,<=,>和>=)。 算术运算符(+, -, *, / 和单值运算_-) duration的最大最小方法(min,max)。 测试duration是不是无限的方法(isFinite)。 Duration可以用以下方法实例化(instantiated)

隐式的经过Int和Long类型转换得来 val d = 100 millis。 经过传递一个Long lengthjava.util.concurrent.TimeUnit。例如val d = Duration(100, MILLISECONDS)。 经过传递一个字符串来表示时间区间,例如 val d = Duration("1.2 µs")。 Duration也提供了unapply方法,所以能够i被用于模式匹配中,例如:

import scala.concurrent.duration._ import java.util.concurrent.TimeUnit._ // instantiation val d1 = Duration(100, MILLISECONDS) // from Long and TimeUnit val d2 = Duration(100, "millis") // from Long and String val d3 = 100 millis // implicitly from Long, Int or Double val d4 = Duration("1.2 µs") // from String // pattern matching val Duration(length, unit) = 5 millis 

更多详细内容参考官网:http://docs.scala-lang.org/overviews/core/futures.html

相关文章
相关标签/搜索