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對象就變成了不可變對象——沒法再被改寫。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")
}
如今咱們知道如何開始一個異步計算來建立一個新的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)。
儘管前文所展現的回調機制已經足夠把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對象。
爲了確保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)
用更多的實用方法來對Futures API進行擴展支持已經被提上了日程,這將爲不少外部框架提供更多專業工具。
正如前面所說的,在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代碼也能夠拋出一個異常。在這種狀況下,這個異常會轉發給調用者。
當異步計算拋出未處理的異常時,與那些計算相關的futures就失敗了。失敗的futures存儲了一個Throwable的實例,而不是返回值。Futures提供onFailure回調方法,它用一個PartialFunction去表示一個Throwable。下列特殊異常的處理方式不一樣:
scala.runtime.NonLocalReturnControl[_]
--此異常保存了一個與返回相關聯的值。一般狀況下,在方法體中的返回結構被調用去拋出這個異常。相關聯的值將會存儲到future或一個promise中,而不是一直保存在這個異常中。
ExecutionException-當由於一個未處理的中斷異常、錯誤或者scala.util.control.ControlThrowable
致使計算失敗時會被存儲起來。這種狀況下,ExecutionException會爲此具備未處理的異常。這些異常會在執行失敗的異步計算線程中從新拋出。這樣作的目的,是爲了防止正常狀況下沒有被客戶端代碼處理過的那些關鍵的、與控制流相關的異常繼續傳播下去,同時告知客戶端其中的future對象是計算失敗的。
更精確的語義描述請參見 [NonFatal]。
到目前爲止,咱們僅考慮了經過異步計算的方式建立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)
將不會實現(即返回一個值或者返回一個異常)。
爲了簡化在併發應用中處理時序(time)的問題,scala.concurrent
引入了Duration抽象。Duration不是被做爲另一個一般的時間抽象存在的。他是爲了用在併發(concurrency)庫中使用的,Duration位於scala.concurrent
包中。
Duration是表示時間長短的基礎類,其能夠是有限的或者無限的。有限的duration用FiniteDuration類來表示,並經過時間長度(length)
和java.util.concurrent.TimeUnit
來構造。無限的durations,一樣擴展了Duration,只在兩種狀況下存在,Duration.Inf
和Duration.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 length
和java.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