【问题标题】:Why is scala Await.result timing out in repl when passed the same future twice?为什么 scala Await.result 在两次通过相同的未来时在 repl 中超时?
【发布时间】:2014-04-16 11:55:52
【问题描述】:

我发现这有点令人困惑。我认为 scala 中的期货是不可变的容器,一旦设置,总是返回相同的值。

所以我有一个未来:

val y = future {Thread.sleep(1000); 1};

现在当我立即(在未来解决之前)将它传递给Await.result 块两次:

Await.result(for (r1 <- y; r2 <- y) yield (r1, r2), 60 seconds)

我收到了TimetoutException

但是,如果我在未来解决后这样做,一切都会正常工作并按预期返回(1,1)

这种行为的原因是什么?

编辑: 我正在使用隐式 ExecutionContext.Implicits.globalscala.concurrent @ scala 2.10.3

编辑2: 如果我创建另一个未来实例做同样的事情并对它们执行 Await.result ,它不会阻塞。

【问题讨论】:

  • 如果 C# 中的 async/await 教会了我一些东西,那就是混合同步和异步等待是一个坏主意,而且往往会导致死锁。我怀疑在阻塞 Await.result、异步未来和阻塞 Thread.sleep 之间存在死锁。
  • @PatrykĆwiek 是的,它过于简单化了,但由于 99% 的期货示例使用 Thread.sleep 我认为应该没有问题。特别是因为无法确定我将来使用的库代码不会自己调用Thread.sleep
  • 这些示例使用Thread.sleep 仅仅是因为标准库中没有直接的工具来安排/暂停未来,所以这是演示需要这种安排的东西的最简单方法。但是你绝对不应该在生产代码中使用它,因为它会阻塞整个线程,完全破坏使用未来的性能优势,而且可能会阻止其他毫无戒心的未来执行(当所有未来的线程都在休眠时)。只是不要这样做。
  • 除非您必须执行文件 I/O 或 DNS 或 JDBC 或调用各种 AWS SDK 类之类的阻塞库或...... “但它通常是一种实际需要。当出现这些情况时,您需要创建自己的 ExecutionContex 并由适当的 ThreadPool 支持,以确保您的系统不会停止、未充分利用 CPU 或其他行为不端。
  • @RégisJean-Gilles 我知道我不应该在生产中使用它,很难确定是否某些第三方解决方案不这样做。另外我想知道为什么会这样?我是不是很不幸,以至于所有期货都由全局执行上下文中的同一个线程执行?这可以解释这种行为。即使在 Thread.sleep 退出时也会发生这种情况,所以我不知道什么可以真正阻止这里的事情。

标签: scala concurrency


【解决方案1】:

这似乎是在 REPL 中执行它的工件。 您甚至可以使用 2 个单独的未来实例来重现它,而无需调用 Thread.sleep, 并且只使用预先实现的期货(这意味着甚至没有涉及任何未来线程)。 是的,认真的:

import scala.concurrent._
import duration._
import ExecutionContext.Implicits.global
val x = Future.successful(1)
val y = Future.successful(2)
Await.result(x.flatMap{_ => y.map{ _ => 0 } }, Duration(10, SECONDS)) // triggers a timeout

有趣的是,如果您将最后一行更改为以下内容,这不会触发任何超时:

Await.result(x.flatMap{_ => Future.successful(2).map{ _ => 0 } }, Duration(10, SECONDS))

似乎罪魁祸首是你的整个代码 sn-p,当在 REPL 中进行评估时,实际上是包装在一个对象中。 这意味着这里的xy实际上是对象的成员,而不是局部变量
更重要的是,对 Await 的调用现在是这个包装对象的构造函数的一部分。 出于某种我尚未调查的原因,似乎是对Await 的调用是在触发阻塞的构造函数中完成的 (您可以通过将此调用包装在一个虚拟类中并对其进行实例化来轻松验证它)。

【讨论】:

  • 是的,您不想从类初始化程序启动线程的原因是类加载中的循环会阻塞。 Scala 对象只是在静态块中创建的一个实例。闭包对 y 的引用是一个循环。在 2.11 中,使用 -Yrepl-class-based 来帮助它工作。
【解决方案2】:

您不想从类初始化程序启动线程的原因是类加载中的循环会阻塞。 Scala 对象只是在静态块中创建的一个实例。闭包对 y 的引用是一个循环。在 2.11 中,使用 -Yrepl-class-based 来帮助它工作。

当闭包类需要X.z时,让我们检查堆栈跟踪是否挂起:

apm@mara:~$ goof
Welcome to Scala version 2.11.0-RC3 (OpenJDK 64-Bit Server VM, Java 1.7.0_25).
Type in expressions to have them evaluated.
Type :help for more information.

scala> import scala.concurrent._
import scala.concurrent._

scala> import duration._
import duration._

scala> import scala.concurrent.ExecutionContext.Implicits.global
import scala.concurrent.ExecutionContext.Implicits.global

scala> object X { val y = Future { 9 } ; val z = Future { 7 } ; val r = Await.result(for (a <- y; b <- z) yield (a+b), 5.seconds) }
defined object X

scala> X.r
java.lang.NoClassDefFoundError: Could not initialize class $line15.$read$$iw$$iw$$iw$$iw$$iw$$iw$X$
    at $line15.$read$$iw$$iw$$iw$$iw$$iw$$iw$X$$anonfun$3.apply(<console>:14)
    at $line15.$read$$iw$$iw$$iw$$iw$$iw$$iw$X$$anonfun$3.apply(<console>:14)
    at scala.concurrent.Future$$anonfun$flatMap$1.apply(Future.scala:251)
    at scala.concurrent.Future$$anonfun$flatMap$1.apply(Future.scala:249)
    at scala.concurrent.impl.CallbackRunnable.run(Promise.scala:32)
    at scala.concurrent.impl.ExecutionContextImpl$AdaptedForkJoinTask.exec(ExecutionContextImpl.scala:121)
    at scala.concurrent.forkjoin.ForkJoinTask.doExec(ForkJoinTask.java:260)
    at scala.concurrent.forkjoin.ForkJoinPool$WorkQueue.runTask(ForkJoinPool.java:1339)
    at scala.concurrent.forkjoin.ForkJoinPool.runWorker(ForkJoinPool.java:1979)
    at scala.concurrent.forkjoin.ForkJoinWorkerThread.run(ForkJoinWorkerThread.java:107)
java.util.concurrent.TimeoutException: Futures timed out after [5 seconds]
  at scala.concurrent.impl.Promise$DefaultPromise.ready(Promise.scala:219)
  at scala.concurrent.impl.Promise$DefaultPromise.result(Promise.scala:223)
  at scala.concurrent.Await$$anonfun$result$1.apply(package.scala:111)
  at scala.concurrent.BlockContext$DefaultBlockContext$.blockOn(BlockContext.scala:53)
  at scala.concurrent.Await$.result(package.scala:111)
  ... 34 elided

只是为了展示它的工作原理:

apm@mara:~$ goof -Yrepl-class-based
Welcome to Scala version 2.11.0-RC3 (OpenJDK 64-Bit Server VM, Java 1.7.0_25).
Type in expressions to have them evaluated.
Type :help for more information.

scala> import scala.concurrent._
import scala.concurrent._

scala> import duration._
import duration._

scala> import scala.concurrent.ExecutionContext.Implicits.global
import scala.concurrent.ExecutionContext.Implicits.global

scala> object X { val y = Future { 9 } ; val z = Future { 7 } ; val r = Await.result(for (a <- y; b <- z) yield (a+b), 5.seconds) }
defined object X

scala> X.r
res0: Int = 16

【讨论】:

  • 我接受了这一点,因为您在 Regis 的回答下对它的赞扬非常完整,包括解决方法。不过,您在他的回答下的评论应该包含在此处。
猜你喜欢
  • 2013-05-13
  • 2021-10-08
  • 2020-09-26
  • 1970-01-01
  • 2020-03-12
  • 2021-02-01
  • 2013-01-05
  • 2017-02-10
  • 1970-01-01
相关资源
最近更新 更多