Handling asynchronous results【翻译】


原文:Handling asynchronous results

让控制器变成异步的

本质上,Play Framework从里到外都是异步的。Play以异步,非阻塞方式处理每一个请求。

默认配置已经为异步控制器做了优化。也就是说,在控制器中,应用代码应该避免阻塞,例如,让控制器代码等待一个操作。这种常见的阻塞操作的例子有JDBC调用,流式API,HTTP请求和长时间的计算。

尽管在默认的执上下文中可以增加线程的数量,让更多并发请求处在阻塞中的控制器执行,但是按照保持控制器异步的推荐方法,可以让线程数据更容易调整并在负载下保证系统的响应。

创建非阻塞Action

由于Play的工作方式,Action代码执行必须尽可能的快,例如,非阻塞。因此,如果我们还不能生产结果,那么我们应该返回什么作为结果呢?答案是一个future 结果!

Future[Result]最终将被用 Result类型的值回填。通过给定的 Future[Result] 代替正常的结果,我可以很快的生成结果而不阻塞。一旦执行完成,Play就会返回结果。

当等待应答时,网络客户端将被阻塞,但是在服务端没有什么会被阻塞,服务端的资源,可以被用来服务其他客户端。

怎样创建Future[Result]

为了创建 Future[Result] ,首先我们还需要一个Future,这个Future将会给我们一个需要计算结果的真实值:

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调用,都会返回给你一个Future。无论你是使用play.api.libs.WS API调用一个外部的Web服务,还是使用Akka 调度一个异步的任务或使用play.api.libs.Akka和Actor通信,都是这样。 这是异步执行的一段代码并得到Future的简单方式:

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

注意:重要的是理解哪个线程代码运行返回Future。在上面的两段代码块中,导入了Play默认的执行环境。这是一个隐式的参数,这个参数传递到所有Future API上接受回调的方法。尽管不一定,但是多数情况下执行上下文相当于线程池。

你不可能只通过在Future中封装就魔术般的把同步线程转成异步的。如果你不能改变应用的架构来避免阻塞操作,在某一时刻操作就一定会被执行,然后线程将会阻塞。 因此为了在Future中封装操作,有必要配置它,让它运行到一个独立的执行上下文中,这个上下文配置了足够多的线程来运行预期的并发。详见 理解Play线程池

使用Actor处理阻塞操作也是有用的。Actor为处理超时和失败提供了一个清理模式,设置阻塞执行上下文,并管理任何可能与服务相关的状态。Actor还提供了类似ScatterGatherFirstCompletedRouter 的模式在一组后端服务器上同时处理缓存和数据库请求并允许远程执行。但是Actor 也许过度的依赖你需要的。

返回Future

到目前为止,虽然我们使用Action.apply构建方法构建Action,但是我们需要使用 Action.async 构建方法来发送一个异步的结果:

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默认是异步的

默认情况下,Play的Action是异步的。例如,在下面的控制器代码中,代码的{ Ok(...) } 部分不是控制器的方法体。它是一个被传递到Action对象的apply 方法的匿名函数,它会创建一个Action类型的对象。本质上,你写的匿名函数将会被调用,并且它的结果会被封装到 Future。

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

注意:Action.apply 和Action.async 都创建Action对象,它们的本质是相同的。有一种单一的Action,它是异步的,不是两种(一种是异步的,一种是同步的)。 .async 构建器只是一个基于返回Future的API,简化创建Action的工具,它可以让开发更容易写非阻塞的代码。

处理超时

通常为了避免如果有什么错误而导致的Web浏览器阻塞并等待, 适当的处理超时很有用。你可以使用 play.api.libs.concurrent.Timeout 在非阻塞超时中封装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")  
}
}

注意:超时不同于取消——即使在超时的情况下,给定的Future仍将完成,哪怕完成的值没有返回。

祝青

继续阅读此作者的更多文章