Scala等待期货序列
我希望下面的代码可以同时等待这两个未来,但事实并非如此Scala等待期货序列,scala,future,Scala,Future,我希望下面的代码可以同时等待这两个未来,但事实并非如此 object Fiddle { val f1 = Future { throw new Throwable("baaa") // emulating a future that bumped into an exception } val f2 = Future { Thread.sleep(3000L) // emulating a future that takes a bit longer to comp
object Fiddle {
val f1 = Future {
throw new Throwable("baaa") // emulating a future that bumped into an exception
}
val f2 = Future {
Thread.sleep(3000L) // emulating a future that takes a bit longer to complete
2
}
val lf = List(f1, f2) // in the general case, this would be a dynamically sized list
val seq = Future.sequence(lf)
seq.onComplete {
_ => lf.foreach(f => println(f.isCompleted))
}
}
val a = FuturesSequence
我假设seq.onComplete
会等到它们全部完成后再自己完成,但事实并非如此;其结果是:
true
false
.sequence
在scala.concurrent.Future的源代码中有点难理解,我想知道如何实现一个等待(动态大小)序列的所有原始未来的并行程序,或者这里可能存在什么问题
编辑:一个相关问题:)由
Future生成的Future
序列。序列
在以下任一情况下完成:
- 所有期货交易均已成功完成,或
- 其中一个期货失败了
第二点是在您的案例中发生的情况,在一个包装的
Future
失败后立即完成是有意义的,因为包装的Future
在失败案例中只能保存一个可丢弃的。等待其他期货没有任何意义,因为结果将是相同的失败。等待所有结果(失败与否)的一种常见方法是在未来将失败“提升”到一个新的表示中,以便所有期货都以某种结果完成(尽管它们可能以表示失败的结果完成)。一个自然的方法就是举起手来试试
提供了一个lifttory
方法,使此操作变得简单,但您可以对标准库的实现执行类似的操作:
import scala.util.{ Failure, Success, Try }
val lifted: List[Future[Try[Int]]] = List(f1, f2).map(
_.map(Success(_)).recover { case t => Failure(t) }
)
现在Future.sequence(lifted)
将在每个Future完成时完成,并将使用Try
表示成功和失败
因此,一个等待一系列期货的所有原始期货的通用解决方案可能如下所示,假设执行上下文当然是隐式可用的
import scala.util.{ Failure, Success, Try }
private def lift[T](futures: Seq[Future[T]]) =
futures.map(_.map { Success(_) }.recover { case t => Failure(t) })
def waitAll[T](futures: Seq[Future[T]]) =
Future.sequence(lift(futures)) // having neutralized exception completions through the lifting, .sequence can now be used
waitAll(SeqOfFutures).map {
// do whatever with the completed futures
}
这是一个支持前面答案的示例。有一种简单的方法可以使用标准的Scala API来实现这一点
在这个例子中,我创建了3个未来。这些将分别在5、7和9秒完成。对wait.result
的调用将被阻止,直到所有问题都得到解决。一旦所有3个期货都完成,a
将设置为List(5,7,9)
并继续执行
此外,如果在任何未来中引发异常,wait.result
将立即取消阻止并引发异常。取消注释异常(…)
行以查看此操作
try {
val a = Await.result(Future.sequence(Seq(
Future({
blocking {
Thread.sleep(5000)
}
System.err.println("A")
5
}),
Future({
blocking {
Thread.sleep(7000)
}
System.err.println("B")
7
//throw new Exception("Ha!")
}),
Future({
blocking {
Thread.sleep(9000)
}
System.err.println("C")
9
}))),
Duration("100 sec"))
System.err.println(a)
} catch {
case e: Exception ⇒
e.printStackTrace()
}
我们可以通过隐式类使用自己的onComplete
方法来丰富Seq[Future[T]]
:
def lift[T](f: Future[T])(implicit ec: ExecutionContext): Future[Try[T]] =
f map { Success(_) } recover { case e => Failure(e) }
def lift[T](fs: Seq[Future[T]])(implicit ec: ExecutionContext): Seq[Future[Try[T]]] =
fs map { lift(_) }
implicit class RichSeqFuture[+T](val fs: Seq[Future[T]]) extends AnyVal {
def onComplete[U](f: Seq[Try[T]] => U)(implicit ec: ExecutionContext) = {
Future.sequence(lift(fs)) onComplete {
case Success(s) => f(s)
case Failure(e) => throw e // will never happen, because of the Try lifting
}
}
}
然后,在特定的MWE中,您可以执行以下操作:
val f1 = Future {
throw new Throwable("baaa") // emulating a future that bumped into an exception
}
val f2 = Future {
Thread.sleep(3000L) // emulating a future that takes a bit longer to complete
2
}
val lf = List(f1, f2)
lf onComplete { _ map {
case Success(v) => ???
case Failure(e) => ???
}}
此解决方案的优点是,您可以像在单个期货上一样,在期货序列上调用一个onComplete
。通过尝试避免额外的障碍来创建未来
implicit val ec = ExecutionContext.global
val f1 = Future {
Try {
throw new Throwable("kaboom")
}
}
val f2 = Future {
Try {
Thread.sleep(1000L)
2
}
}
Await.result(
Future.sequence(Seq(f1, f2)), Duration("2 sec")
) foreach {
case Success(res) => println(s"Success. $res")
case Failure(e) => println(s"Failure. ${e.getMessage}")
}
虽然这是一个很老的问题,但这就是我最近运行它的方式
object Fiddle {
val f1 = Future {
throw new Throwable("baaa") // emulating a future that bumped into an exception
}
val f2 = Future {
Thread.sleep(3000L) // emulating a future that takes a bit longer to complete
2
}
val lf = List(f1, f2) // in the general case, this would be a dynamically sized list
val seq = Future.sequence(lf)
import scala.concurrent.duration._
Await.result(seq, Duration.Inf)
}
这不会完成,将等待所有未来完成。您可以根据您的用例更改等待时间。我将它保持为无限,这是我的情况所需要的。谢谢,对了,我应该从.sequence
类型的一元属性中收集这种行为。但关键是我想等待所有这些都完成;使用.sequence
只是一种(错误的)方法。我该如何最直接地做到这一点?这并不能真正回答问题的第二部分。想要收集所有结果是完全合理的,不管失败与否,很遗憾标准库的未来实现没有类似的东西来促进这一点。@TravisBrown不需要在stdlib futures中专门使用这种方法:f.transform(Success())@TravisBrown Yep@ViktorKlang…除非你是成千上万的Scala开发人员中的一员,在未来的几年里,你将一直使用或至少支持2.11。非常感谢,我可能会使用并调整这个功能!您认为使用Try
从返回值的执行结果|流控制异常
映射到成功|失败
返回值是准确的吗?顺便说一句,我可以非常平等地实现相同的转换,使用承诺而不是Try
。在我需要扩展未来特性(用隐式类拉皮条)的地方,我已经放弃了承诺。在这里,你几乎肯定不想要承诺Try
本质上是一种同步的未来
,在这种情况下,您只有两种状态(不满意、成功、失败),这正好符合此问题。希望我们在新系列中不需要这种诡计library@matanster,在新馆藏库的开发过程中,是否有什么特别之处让您希望新馆藏库可以让我们以更少的欺骗实现同样的行为?