我希望像下面这样的代码会等待两个期货,但事实并非如此。
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
在pala.concurrent.Future的源代码中, .sequence
有点难以理解,我想知道如何实现一个等待(动态大小)序列的所有原始未来的并行,或者可能是什么这里的问题。
编辑:相关问题:https://worldbuilding.stackexchange.com/questions/12348/how-do-you-prove-youre-from-the-future:)
答案 0 :(得分:36)
等待所有结果(失败与否)的一种常见方法是将失败“提升”到未来的新表示中,以便所有期货都能得到一些结果(尽管它们可能会以表示失败的结果完成) 。一种自然的方法就是提升到Try
。
Twitter's implementation of futures提供了一个liftToTry
方法,可以解决这个问题,但您可以使用标准库的实现做类似的事情:
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)
将在每个未来完成时完成,并使用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
}
答案 1 :(得分:19)
Future
生成的Future.sequence
在以下情况之一完成:
第二点是你的情况正在发生的事情,只要其中一个被包裹的Future
失败就完成了,因为包裹Future
只能容纳一个{{1}在失败的情况下。等待其他期货没有意义,因为结果将是同样的失败。
答案 2 :(得分:3)
这是支持上一个答案的示例。只需使用标准的Scala API即可轻松实现此目的。
在这个例子中,我创造了3个期货。这些将分别在5秒,7秒和9秒完成。对Await.result
的调用将阻止,直到所有期货都已解决。完成所有3个期货后,a
将设置为List(5,7,9)
,执行将继续。
此外,如果在任何期货中抛出异常,Await.result
将立即解除阻止并抛出异常。取消注释Exception(...)
行以查看此操作。
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()
}
答案 3 :(得分:1)
我们可以通过隐式类使用自己的Seq[Future[T]]
方法来丰富onComplete
:
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
。
答案 4 :(得分:1)
尽管这是一个很老的问题,但这是我最近一次如何运行它。
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)
}
这将不会完成,将等到所有将来完成。您可以根据用例更改等待时间。我将其保持为无限,这是我所需要的。
答案 5 :(得分:0)
尝试通过避免不必要的麻烦创造未来。
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}")
}