【三 异步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),即便完成的值没有被返回。

相关文章
相关标签/搜索