1. 程式人生 > >Scala之Future

Scala之Future

保存 發送 都是 tab session 占位符 失敗 table conn

一、簡介

Future提供了一套高效便捷的非阻塞並行操作管理方案。其基本思想很簡單,所謂Future,指的是一類占位符對象,用於指代某些尚未完成的計算的結果。一般來說,由Future指代的計算都是並行執行的,計算完畢後可另行獲取相關計算結果。以這種方式組織並行任務,便可以寫出高效、異步、非阻塞的並行代碼。

所謂Future,是一種用於指代某個尚未就緒的值的對象。而這個值,往往是某個計算過程的結果:

(1)若該計算過程尚未完成,我們就說該Future未就位;

(2)若該計算過程正常結束,或中途拋出異常,我們就說該Future已就位。

Future的就位分為兩種情況:

(1)當Future帶著某個值就位時,我們就說該Future攜帶計算結果成功就位。

(2)當Future因對應計算過程拋出異常而就緒,我們就說這個Future因該異常而失敗。

Future的一個重要屬性在於它只能被賦值一次。一旦給定了某個值或某個異常,future對象就變成了不可變對象——無法再被改寫。

二、創建Future

創建future對象最簡單的方法是調用future方法,該future方法啟用異步(asynchronous)計算並返回保存有計算結果的futrue,一旦該future對象計算完成,其結果就變的可用。

下面,我們舉一個例子來說明。假設我們使用某些流行的社交網絡的假定API獲取某個用戶的朋友列表,我們將打開一個新對話(session),然後發送一個請求來獲取某個特定用戶的好友列表。

import scala.concurrent._
import ExecutionContext.Implicits.global

val session = socialNetwork.createSessionFor("user", credentials)
val f: Future[List[Friend]] = Future {
session.getFriends()
}

在上述例子中,然後我們初始化一個session變量來用作向服務器發送請求,用一個假想的 createSessionFor 方法來返回一個List[Friend]。為了獲得朋友列表,我們必須通過網絡發送一個請求,這個請求可能耗時很長。這能從調用getFriends方法得到解釋。為了更好的利用CPU,響應到達前不應該阻塞(block)程序的其他部分執行,於是在計算中使用異步。future方法就是這樣做的,它並行地執行指定的計算塊,在這個例子中是向服務器發送請求和等待響應。一旦服務器響應,future f 中的好友列表將變得可用。

三、回調函數(Callbacks)

我們都知道Java中的Future並不是全異步的,當你需要Future裏的值的時候,你只能用get去獲取它,亦或者不斷訪問Future的狀態,若完成再去取值,但其意義上便不是真正的異步了,它在獲取值的時候是一個阻塞的操作,當然也就無法執行其他的操作,直到結果返回。

但是在Scala中雖然也可以這麽做,但是不推薦,因為Scala的Future提供了回調函數來獲取它的結果。看如下例子:

val fut = Future {
Thread.sleep(1000)
1 + 1
}
fut onComplete {
case Success(r) => println(s"the result is ${r}")
case _ => println("some Exception")
}
println("I am working")
Thread.sleep(2000)

執行結果如下:

I am working
the result is 2

從結果中可以看出,我們在利用Callback方式來獲取Future結果的時候並不會阻塞,而只是當Future完成後會自動調用onComplete,我們只需要根據它的結果再做處理即可,而其他互不依賴的操作可以繼續執行不會阻塞。

四、Future組合

前面我們講的較多的都是單個Future的情況,但是在真正實際應用時往往會遇到多個Future的情況,那麽在Scala中是如何處理這種情況的呢?

我們首先來看下面這個例子,假設我們有一個用於進行貨幣交易服務的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")
}
}

從上面的代碼中,我們可以看出,為了實現功能,我們將不得不在onSuccess的回調中重復這個模式,從而可能使代碼過度嵌套,過於冗長,並且難以理解。另一方面,purchase只是定義在局部範圍內–它只能被來自onSuccess內部的回調響應。這也就是說,這個應用的其他部分看不到purchase,而且不能為它註冊其他的onSuccess回調,比如說賣掉些別的貨幣。

為解決上述的兩個問題,futures提供了組合器(combinators)來使之具有更多易用的組合形式。映射(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方法上的映射了。除了map組合器,Ftuture還提供了Future還擁有flatMap,filter和foreach等組合器。


Scala之Future