Play Framework 异步控制器阻止对同一控制器的后续调用

Play Framework async controller blocks subsequent calls for the same controller

我的目标是从异步控制器进行一些数据库查询,然后 return 得到答案。

我正在玩这个示例项目,现在只是通过睡眠模拟数据库查询,但我注意到无论我做什么,REST 接口甚至都不会开始第二个查询的睡眠,直到第一个完成。 例如:如果我从浏览器中的一个选项卡调用 REST 接口,然后 1 秒后再次从另一个选项卡调用,我希望第二个选项卡也能在 10 秒内得到回复,但实际上是 19.

它似乎也没有使用“database-io”池:

1: application-akka.actor.default-dispatcher-2

2: application-akka.actor.default-dispatcher-5

我的代码:

@Singleton
class AsyncController @Inject()(cc: ControllerComponents, actorSystem: ActorSystem) extends AbstractController(cc) {

  implicit val executionContext = actorSystem.dispatchers.lookup("database-io")

  def message = Action.async {
    getFutureMessage().map { msg => Ok(msg) }
  }

  private def getFutureMessage(): Future[String] = {
    val defaultThreadPool = Thread.currentThread().getName;
    println(s"""1: $defaultThreadPool""")

    val promise: Promise[String] = Promise[String]()
    actorSystem.scheduler.scheduleOnce(0 second) {
      val blockingPool = Thread.currentThread().getName;
      println(s"""2: $blockingPool""")

      Thread.sleep(10000)
      promise.success("Hi!")
    }(actorSystem.dispatcher)
    promise.future
  }

}

这种行为可能有两个原因:

  1. 您使用的是开发模式(1线程),或者您的产品配置只配置了一个线程。
  2. 浏览器阻止第二个请求,直到收到第一个请求的响应。这句话:"If I call the REST interface from one tab in the browser." 尝试从不同的浏览器做同样的事情。

您需要避免阻塞代码。基本上:

  1. 你可以用一种方法 returns 未来。

  2. 你映射进去

  3. 您可以恢复 Future 结果可能带来的任何失败。

假设我有:

def userAge (userId: String): Future[Int] = ???

然后你映射进去:

userAge.map{
  age => ??? //everything is ok
}.recover{ case e: Throwable => ??? //Do something when it fails 

请注意,如果您有多个呼叫,另一个 map 将变为 flatMap,因为您想要 Future[...] 而非 Future[Future[...]]