原文:Handling asynchronous results
讓控制器變成異步的
本質(zhì)上,Play Framework從里到外都是異步的。Play以異步,非阻塞方式處理每一個(gè)請(qǐng)求。
默認(rèn)配置已經(jīng)為異步控制器做了優(yōu)化。也就是說,在控制器中,應(yīng)用代碼應(yīng)該避免阻塞,例如,讓控制器代碼等待一個(gè)操作。這種常見的阻塞操作的例子有JDBC調(diào)用,流式API,HTTP請(qǐng)求和長時(shí)間的計(jì)算。
盡管在默認(rèn)的執(zhí)上下文中可以增加線程的數(shù)量,讓更多并發(fā)請(qǐng)求處在阻塞中的控制器執(zhí)行,但是按照保持控制器異步的推薦方法,可以讓線程數(shù)據(jù)更容易調(diào)整并在負(fù)載下保證系統(tǒng)的響應(yīng)。
創(chuàng)建非阻塞Action
由于Play的工作方式,Action代碼執(zhí)行必須盡可能的快,例如,非阻塞。因此,如果我們還不能生產(chǎn)結(jié)果,那么我們應(yīng)該返回什么作為結(jié)果呢?答案是一個(gè)future 結(jié)果!
Future[Result]最終將被用 Result類型的值回填。通過給定的 Future[Result] 代替正常的結(jié)果,我可以很快的生成結(jié)果而不阻塞。一旦執(zhí)行完成,Play就會(huì)返回結(jié)果。
當(dāng)?shù)却龖?yīng)答時(shí),網(wǎng)絡(luò)客戶端將被阻塞,但是在服務(wù)端沒有什么會(huì)被阻塞,服務(wù)端的資源,可以被用來服務(wù)其他客戶端。
怎樣創(chuàng)建Future[Result]
為了創(chuàng)建 Future[Result] ,首先我們還需要一個(gè)Future,這個(gè)Future將會(huì)給我們一個(gè)需要計(jì)算結(jié)果的真實(shí)值:
import play.api.libs.concurrent.Execution.Implicits.defaultContext
val futurePIValue: Future[Double] = computePIAsynchronously()
val futureResult: Future[Result] = futurePIValue.map { pi =>
Ok("PI value computed: " + pi)
}
Play所有的異步API調(diào)用,都會(huì)返回給你一個(gè)Future。無論你是使用play.api.libs.WS API調(diào)用一個(gè)外部的Web服務(wù),還是使用Akka 調(diào)度一個(gè)異步的任務(wù)或使用play.api.libs.Akka和Actor通信,都是這樣。 這是異步執(zhí)行的一段代碼并得到Future的簡(jiǎn)單方式:
val futureInt: Future[Int] = scala.concurrent.Future {
intensiveComputation()
}
注意:重要的是理解哪個(gè)線程代碼運(yùn)行返回Future。在上面的兩段代碼塊中,導(dǎo)入了Play默認(rèn)的執(zhí)行環(huán)境。這是一個(gè)隱式的參數(shù),這個(gè)參數(shù)傳遞到所有Future API上接受回調(diào)的方法。盡管不一定,但是多數(shù)情況下執(zhí)行上下文相當(dāng)于線程池。
你不可能只通過在Future中封裝就魔術(shù)般的把同步線程轉(zhuǎn)成異步的。如果你不能改變應(yīng)用的架構(gòu)來避免阻塞操作,在某一時(shí)刻操作就一定會(huì)被執(zhí)行,然后線程將會(huì)阻塞。 因此為了在Future中封裝操作,有必要配置它,讓它運(yùn)行到一個(gè)獨(dú)立的執(zhí)行上下文中,這個(gè)上下文配置了足夠多的線程來運(yùn)行預(yù)期的并發(fā)。詳見 理解Play線程池 。
使用Actor處理阻塞操作也是有用的。Actor為處理超時(shí)和失敗提供了一個(gè)清理模式,設(shè)置阻塞執(zhí)行上下文,并管理任何可能與服務(wù)相關(guān)的狀態(tài)。Actor還提供了類似ScatterGatherFirstCompletedRouter 的模式在一組后端服務(wù)器上同時(shí)處理緩存和數(shù)據(jù)庫請(qǐng)求并允許遠(yuǎn)程執(zhí)行。但是Actor 也許過度的依賴你需要的。
返回Future
到目前為止,雖然我們使用Action.apply構(gòu)建方法構(gòu)建Action,但是我們需要使用 Action.async 構(gòu)建方法來發(fā)送一個(gè)異步的結(jié)果:
import play.api.libs.concurrent.Execution.Implicits.defaultContext
def index = Action.async {
val futureInt = scala.concurrent.Future { intensiveComputation() }
futureInt.map(i => Ok("Got result: " + i))
}
Action默認(rèn)是異步的
默認(rèn)情況下,Play的Action是異步的。例如,在下面的控制器代碼中,代碼的{ Ok(...) } 部分不是控制器的方法體。它是一個(gè)被傳遞到Action對(duì)象的apply 方法的匿名函數(shù),它會(huì)創(chuàng)建一個(gè)Action類型的對(duì)象。本質(zhì)上,你寫的匿名函數(shù)將會(huì)被調(diào)用,并且它的結(jié)果會(huì)被封裝到 Future。
def echo = Action { request =>
Ok("Got request [" + request + "]")
}
注意:Action.apply 和Action.async 都創(chuàng)建Action對(duì)象,它們的本質(zhì)是相同的。有一種單一的Action,它是異步的,不是兩種(一種是異步的,一種是同步的)。 .async 構(gòu)建器只是一個(gè)基于返回Future的API,簡(jiǎn)化創(chuàng)建Action的工具,它可以讓開發(fā)更容易寫非阻塞的代碼。
處理超時(shí)
通常為了避免如果有什么錯(cuò)誤而導(dǎo)致的Web瀏覽器阻塞并等待, 適當(dāng)?shù)奶幚沓瑫r(shí)很有用。你可以使用 play.api.libs.concurrent.Timeout 在非阻塞超時(shí)中封裝Future :
import play.api.libs.concurrent.Execution.Implicits.defaultContext
import scala.concurrent.duration._
import play.api.libs.concurrent.Timeout
def index = Action.async {
Timeout.timeout(actorSystem, 1.seconds) {
intensiveComputation().map { i =>
Ok("Got result: " + i)
}
}.recover {
case e: TimeoutException =>
InternalServerError("timeout")
}
}
注意:超時(shí)不同于取消——即使在超時(shí)的情況下,給定的Future仍將完成,哪怕完成的值沒有返回。