文档

§处理异步结果

§使控制器异步

在内部,Play 框架从底层到顶层都是异步的。Play 以异步、非阻塞的方式处理每个请求。

默认配置针对异步控制器进行了优化。换句话说,应用程序代码应避免在控制器中阻塞,即控制器代码不应该等待操作完成。此类阻塞操作的常见示例包括 JDBC 调用、流式 API、HTTP 请求和长时间计算。

虽然可以增加默认执行上下文中的线程数量,以允许更多并发请求由阻塞控制器处理,但遵循将控制器保持异步的推荐方法可以更轻松地进行扩展,并在负载下保持系统响应。

§创建非阻塞操作

由于 Play 的工作方式,操作代码必须尽可能快,即非阻塞。那么,如果我们还无法生成结果,应该返回什么作为结果呢?答案是未来结果!

Future[Result] 最终将用类型为 Result 的值兑现。通过提供 Future[Result] 而不是普通的 Result,我们能够快速生成结果,而不会阻塞。Play 然后将在承诺兑现后立即提供结果。

Web 客户端将在等待响应时被阻塞,但服务器上不会有任何阻塞,并且服务器资源可以用于为其他客户端提供服务。

使用 Future 只是其中的一部分!如果您正在调用 JDBC 等阻塞 API,那么您仍然需要让您的 ExecutionStage 使用不同的执行器运行,以将其从 Play 的渲染线程池中移出。您可以通过创建 play.api.libs.concurrent.CustomExecutionContext 的子类来实现,该子类引用 自定义调度器

import play.api.libs.concurrent.CustomExecutionContext

// Make sure to bind the new context class to this trait using one of the custom
// binding techniques listed on the "Scala Dependency Injection" documentation page
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:这个 future 将提供我们计算结果所需的实际值。


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 服务,还是使用 Pekko 来调度异步任务或使用 play.api.libs.Pekko 与 actor 进行通信,都是如此。

以下是一种简单的方法,可以异步执行代码块并获取 Future

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

注意:了解 future 在哪个线程上运行代码非常重要。在上面的两个代码块中,有一个对 Play 默认执行上下文的导入。这是一个隐式参数,它被传递给 future API 上接受回调的所有方法。执行上下文通常等同于线程池,但并非总是如此。

您不能通过将同步 IO 包装在 Future 中来神奇地将其转换为异步。如果您无法更改应用程序的架构以避免阻塞操作,那么在某个时刻,该操作将必须执行,并且该线程将被阻塞。因此,除了将操作包含在 Future 中之外,还需要将其配置为在已配置有足够线程来处理预期并发性的单独执行上下文中运行。有关更多信息,请参阅 了解 Play 线程池,并下载 Play 示例模板,其中展示了数据库集成。

对于阻塞操作,使用 Actor 也很有用。Actor 为处理超时和故障、设置阻塞执行上下文以及管理与服务相关的任何状态提供了一个干净的模型。此外,Actor 还提供诸如 ScatterGatherFirstCompletedRouter 之类的模式,以解决同时缓存和数据库请求,并允许在后端服务器集群上进行远程执行。但是,根据您的需求,Actor 可能过于复杂。

§返回 future

虽然我们一直在使用 Action.apply 构建器方法来构建操作,但要发送异步结果,我们需要使用 Action.async 构建器方法。

def index = Action.async {
  val futureInt = scala.concurrent.Future { intensiveComputation() }
  futureInt.map(i => Ok("Got result: " + i))
}

§默认情况下,操作是异步的

Play 操作 默认情况下是异步的。例如,在下面的控制器代码中,{ Ok(...) } 部分代码不是控制器的函数体。它是一个匿名函数,被传递给 Action 对象的 apply 方法,该方法创建一个 Action 类型的对象。在内部,您编写的匿名函数将被调用,其结果将被封装在一个 Future 中。

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

注意: Action.applyAction.async 都创建以相同方式在内部处理的 Action 对象。只有一种 Action,它是异步的,而不是两种(同步的和异步的)。.async 构建器只是一个简化基于返回 Future 的 API 创建操作的工具,这使得编写非阻塞代码更容易。

§处理超时

正确处理超时通常很有用,以避免在出现问题时让 Web 浏览器阻塞并等待。您可以使用 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 { i => Ok("Got result: " + i) }
    .recover {
      case e: scala.concurrent.TimeoutException =>
        InternalServerError("timeout")
    }
}

注意: 超时与取消不同 - 即使在超时的情况下,给定的 Future 仍然会完成,即使该完成值没有被返回。

下一步: 流式 HTTP 响应


发现此文档中的错误?此页面的源代码可以在 此处 找到。阅读完 文档指南 后,请随时贡献拉取请求。有疑问或建议要分享?转到 我们的社区论坛 与社区进行对话。