Handling asynchronous results

构建异步controller

Play框架本身就是异步的。对每一个请求而言,Play都会使用异步,非阻塞的方式来处理。

默认配置下controller即是异步的了。换言之,应用应该避免在controller中做阻塞操作,例如,让controller等待某一个操作完成。类似的阻塞操作还有:调用JDBC,流处理(streaming)API,HTTP请求和长时间的计算任务。

虽然不可能通过增加默认执行环境(execution context)中的线程数来提升阻塞controller的并发请求处理能力,但使用异步controller可以使应用更易于扩展,在负载较大的情况下依然能够保持响应。

创建非阻塞action

由于Play的异步工作方式,action应该做到尽可能的快,例如非阻塞。那么,在还没有得到返回结果的情况下,应该返回什么作为结果呢?答案是future结果!

一个Future[Result]最终会被替换成一个类型为Result的值。通过提供Future[Result]而非Result,我们能够在非阻塞的情况下快速生成结果。一旦从promise中得到了最终结果,Play就会应用该结果。

Web客户端在等待响应时依然是阻塞的,但在服务器端没有任何操作被阻塞,而且服务器资源依然可以服务其他客户端。

如何创建Future[Result]

在创建一个Future[Result]之前,我们需要先创建另一个future:这个future会返回一个真实的值,以便我们依此生成结果:

  1. import play.api.libs.concurrent.Execution.Implicits.defaultContext
  2. val futurePIValue: Future[Double] = computePIAsynchronously()
  3. val futureResult: Future[Result] = futurePIValue.map { pi =>
  4. Ok("PI value computed: " + pi)
  5. }

所有的Play异步API调用都会返回一个Future。不管你是在调用外部web服务,如play.api.libs.WSAPI,或是用Akka调度异步任务,亦或是使用play.api.libs.Akka与actor进行通讯。

以下是一个异步调用并获得一个Future的简单例子:

  1. import play.api.libs.concurrent.Execution.Implicits.defaultContext
  2. val futureInt: Future[Int] = scala.concurrent.Future {
  3. intensiveComputation()
  4. }

注意:理解哪个线程运行了future非常重要,以上的两段代码都导入了Play的默认执行环境(execution context)。这是一个隐式(implicit)的参数,会被传入所有接受回调的future API方法中。执行环境(execution context)通常等价于线程池,但这并不是一定的。

简单的把同步IO封装入Future并不能将其转换为异步的。如果你不能通过改变应用架构来避免阻塞操作,那么该操作总会在某一时刻被执行的,而相应的线程则会被阻塞。所以,除了将操作封装于Future中,还必须让它运行在配置了足够线程来处理可预计并发的独立执行环境(execution context)中。更多信息请见理解Play线程池

这对于使用Actor来阻塞操作也非常有用。Actor提供了一种非常简洁的模型来处理超时和失败,设置阻塞执行环境(execution context),并且管理了该服务的一切状态。Actor还提供了像ScatterGatherFirstCompletedRouter这样的模式来处理同时缓存和数据库请求,并且能够远程执行于后端服务器集群中。但使用actor可能有些多余,主要还是取决你想要的是什么。

返回future

迄今为止,我们一直都在调用Action.apply方法来构建action,为了发出一个异步的结果,我们需要调用Action.async

  1. import play.api.libs.concurrent.Execution.Implicits.defaultContext
  2. def index = Action.async {
  3. val futureInt = scala.concurrent.Future { intensiveComputation() }
  4. futureInt.map(i => Ok("Got result: " + i))
  5. }

Action默认即是异步的

Play的action默认即是异步的。比如说,在下面这个controller中,{ Ok(...) }这部分代码并不是controller的方法体。这其实是一个传入Action对象apply方法的匿名函数,用来创建一个Action对象。运行时,你写的这个匿名函数会被调用并返回一个Future结果。

  1. val echo = Action { request =>
  2. Ok("Got request [" + request + "]")
  3. }

注意:Action.applyAction.async创建的Action对象在Play内部会以同样的方式处理。他们都是异步的Action,而不是一个同步一个异步。.async构造器只是用来简化创建基于API并返回Future的action,让非阻塞的代码更加容易写。

处理超时

能够合理的处理超时通常很有用,当出现问题时可以避免浏览器无谓的阻塞和等待。简单的通过调用promise超时来构造另一个promise便能够处理这些情况:

  1. import play.api.libs.concurrent.Execution.Implicits.defaultContext
  2. import scala.concurrent.duration._
  3. def index = Action.async {
  4. val futureInt = scala.concurrent.Future { intensiveComputation() }
  5. val timeoutFuture = play.api.libs.concurrent.Promise.timeout("Oops", 1.second)
  6. Future.firstCompletedOf(Seq(futureInt, timeoutFuture)).map {
  7. case i: Int => Ok("Got result: " + i)
  8. case t: String => InternalServerError(t)
  9. }
  10. }