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

转载于:https://my.oschina.net/landas/blog/2250793

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值