(Scala)中的异步请求播放“挂起”

时间:2013-07-31 23:47:36

标签: scala playframework-2.0

我已经编写了一个应用程序(Scala)Play,当我在本地计算机上运行它时,它的工作方式与我预期的完全相同。我正在处理的方法(可能有其他人遇到此问题,我没有进行过广泛检查)是一个由POST请求触发的方法。它返回一个Async对象,因为该方法完成的评估返回Future。

但是当我部署它时,这种方法的表现完全不同。我一直在追逐这一切,但我已经到了无法解释发生了什么的地步。

该方法如下所示:

  def add = Action(parse.json) { request =>
    Async {
      val req = request.body.asOpt[TaskRequest] map { r =>
        val job = createJob(r)
        submitJob(job, r)
        job map { ij =>
          allowOrigin(Created(ij).withHeaders("Location" -> routes.Jobs.read(ij._id.stringify).url));
        }
      }
      req.get
    }
  }

总的来说,这很简单。 createJob方法返回Future[Job]submitJob调用是一种“即发即忘”调用来触发一些背景内容(我不想等待)。然后,当job 创建时,我会处理Result结果以生成job个对象。

所有这些都像我在本地机器上所描述的那样工作。但是当我部署它时,真的很奇怪。 submitJob调用会触发长时间运行的计算。正如我所说,我对等待它不感兴趣,这就是为什么我甚至不使用它返回任何地方的Future

但这是奇怪的部分...在我的开发机器上,我的POST请求(直接调用add)的响应在作业创建后立即返回。但是在部署机器上,我只在submitJob任务完成后得到响应!换句话说,我将此req作为Future看似与submitJob调用完全无关(它当然不依赖于其结果),但在部署中环境Async阻止在submitJob完成后才会返回(即使req几乎立即完成)。

这到底是怎么回事?我在代码中放了println个语句。它几乎可以立即通过req.get电话。但总体(HTTP)响应肯定会等到submitJob完成。 Async以某种方式了解submitJob电话吗?如果是这样,为什么它在两台不同的机器上表现不同?

有什么想法吗?

2 个答案:

答案 0 :(得分:4)

好的,我明白了。哦,这是多么痛苦。问题是我的“一劳永逸”任务是一项长期任务。但是部署机器上的线程池大小最终小于我的开发机器。结果,这个长时间运行的任务在线程池中丢失并堵塞了它。因此,AsyncResult的处理可能会被长时间运行的计算所取代(它实际上阻止了长时间运行的计算结果,但最重要的是它被阻止了。)

在阅读Heather Miller对Scala 2.10期货的一个非常好的解释之后,我注意到blocking构造可用于临时扩展线程池的大小以用于单个未来。我用blocking调用包裹我的阻止代码,这就是诀窍。据推测,这会分配一个额外的临时工来处理我的阻塞调用,因此AsyncResult内容得到及时处理。

(注意,包裹submitJob调用是没用的,因为它本身并没有阻止。我必须做的是进入submitJob调用并找到实际阻塞的地方并包装。)

答案 1 :(得分:3)

虽然你已经想通了,但还有另一种选择。当您使用say scala的默认ThreadPool即scala.concurrent.ExecutionContext.Implicits.global时,通常会发生这种情况。它使用newFixedSizeThreadPool,其大小为number of cores

另一种方法是使用您自己的ExecutionContext以及以上的未来电话。即在上面的代码中,添加以下行:

implicit val exec = ExecutionContext.fromExecutor(Executors.newCachedThreadPool())

您的所有future来电都将使用上述ExecutionContext