【三 異步HTTP編程】 1. 處理異步results

異步results

事實上整個Play框架都是異步的。Play非阻塞地處理每一個request請求。html

默認的配置適配的正是異步的controller。所以開發者應該盡力避免在在controller中阻塞,如在controller方法中等待其餘的操做。常見的例子如:JDBC調用、流式API、HTTP請求以及長時間的計算等。web

雖然能夠經過提供併發線程數來容許阻塞式的controller來處理更多的請求,可是使用異步controller在高負載時的擴展性及簡便性上更有優點。數據庫

建立非阻塞的actions

Play的工做模式決定了Action的速度必需要儘量的快(非阻塞)。那麼當咱們尚未生成返回值時如何返回呢?其實 response 是一個 future result!後端

Future[Result] 表明將來某時刻的Result。經過返回 Future 來取消當前的阻塞,Play將會盡量快的返回真正的Result。api

雖然web客戶端在等待響應的時候會阻塞,可是服務端不會有任何阻塞,資源能夠獲得最大程度的重用。緩存

僅僅使用 Future 不是所有!若是調用阻塞的API,例如JDBC,那麼你須要在不一樣的 excutor 中運行本身的 ExecutionStage,而不是使用Play的 rendering 線程池。這時你能夠創造一個帶 custom dispatcher 引用的 play.api.libs.concurrent.CustomExecutionContext 子類:服務器

import play.api.libs.concurrent.CustomExecutionContext

trait MyExecutionContext extends ExecutionContext

class MyExecutionContextImpl @Inject()(system: ActorSystem)
  extends CustomExecutionContext(system, "my.executor") with MyExecutionContext

class HomeController @Inject()(myExecutionContext: MyExecutionContext, val controllerComponents: ControllerComponents) extends BaseController {
  def index = Action.async {
    Future {
      // Call some blocking API
      Ok("result of blocking call")
    }(myExecutionContext)
  }
}

想要優化自定義的線程池能夠查看 ThreadPools網絡

如何建立 Future[Result]

建立 Future[Result] 以前咱們必須首先建立另外一個Future,用它來提供計算最終Result所須要的參數:架構

val futurePIValue: Future[Double] = computePIAsynchronously()
val futureResult: Future[Result] = futruePIValue.map { pi =>
    Ok("PI value computed: " + pi)
}

Play 全部的異步API調用都會返回 Future,不管你用 play.api.libs.WS 調用外部接口,或者利用Akka來調度異步任務,仍是使用 play.api.libs.Akka 來和 actores通訊。併發

下面是異步獲取Future的一個代碼塊例子:

val futureInt: Future[Int] = scala.concurrent.Future {
    intensiveComputation()
}

注意:理解線程返回future的部分是很重要的。上面兩段代碼中,默認導入了Play的execution context。它其實是以回調做爲參數的 future API 接收到的一個隱式參數。此execution context常常能夠等同於一個線程池,雖然這並非必須的。

你可使用Future來將同步IO簡單地包裝爲異步。但若是你沒法從application架構上調整來避免阻塞,在某一時刻它仍然會被阻塞住。想將操做完全異步化,你須要使用一個獨立的execution context,併爲之配置好足夠的線程以應對併發需求。請查看 理解Play線程池 瞭解細節, play模板樣例 展現了數據庫集成。

Actor對於阻塞操做仍然是有意義的。它提供了整潔的方式來處理超時和異常,設置阻塞的execution context,以及管理服務相關的狀態。此外,Actor還提供了ScatterGatherFirstCompletedRouter來處理同步緩存和數據庫請求,並容許在後端服務器集羣上遠程執行代碼。固然是否使用Actor要取決於你的需求,也不要過分的使用它。

返回Future

目前爲止咱們只用 Action.apply 構造器方法來構建action,爲了發送異步result,咱們須要使用 Action.async 構造器方法:

def index = Action.async {
    val futureInt = scala.concurrent.Future { intensiveComputation()}
    futureInt.map(i => Ok("Got result: " + i))
}

默認的Action是異步的

Play actions 是默認異步的。例以下面的controller, { Ok(...)}  並非 controller 的方法體。它是傳遞到Action object的apply方法的一個匿名函數,將由它建立Action。Play內部會調用你建立的匿名函數並將返回的result封裝進Future。

def echo = Action { request =>
    Ok("Got request [" + request + "]")
}

注意:不論是Action.apply仍是Action.async建立的Action其內部機制都是同樣的。僅僅只有一種異步Action,而不是同步和異步兩種。.async構造器僅僅是返回Future的簡單方式,它讓你更容易地寫出非阻塞的代碼。

處理超時

超時處理的重要性不言而喻,它能夠避免錯誤發生時的網絡阻塞。你可使用 play.api.libs.concurrent.Futures 來包裝Future,爲其增長非阻塞的超時處理。

import scala.concurrent.duration._
import play.api.libs.concurrent.Futures._

def index = Action.async {
    // You will need an implicit Futures for withTimeout() -- you usually get
    // that by injecting it into your controller's constructor
    intensiveComputation().withTimeout(1.seconds).map {
        Ok("Got result: " + i)
    }.recover {
        case e: scala.concurrent.TimeoutException =>
            InternalServerError("timeout")
    }
}

注意:超時和取消是不一樣的 —— 超時仍然屬於完成(complete),即便完成的值沒有被返回。

相關文章
相關標籤/搜索