斯卡拉等待期货序列

时间:2015-03-30 10:59:17

标签: 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 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:)

6 个答案:

答案 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}")
}