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

異步results

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

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

雖然可以通過提供併發線程數來允許阻塞式的controller來處理更多的請求,但是使用異步controller在高負載時的擴展性及簡便性上更有優勢。

創建非阻塞的actions

Play的工作模式決定了Action的速度必須要儘可能的快(非阻塞)。那麼當我們還沒有生成返回值時如何返回呢?其實 response 是一個 future result!

Future[Result] 代表未來某時刻的Result。通過返回 Future 來取消當前的阻塞,Play將會儘可能快的返回真正的Result。

雖然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),即使完成的值沒有被返回。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章