How to wait for several Futures?

前端 未结 8 832
故里飘歌
故里飘歌 2020-12-02 06:02

Suppose I have several futures and need to wait until either any of them fails or all of them succeed.

For example: Let there are 3 futures:

相关标签:
8条回答
  • 2020-12-02 06:12

    Here is a solution without using actors.

    import scala.util._
    import scala.concurrent._
    import java.util.concurrent.atomic.AtomicInteger
    
    // Nondeterministic.
    // If any failure, return it immediately, else return the final success.
    def allSucceed[T](fs: Future[T]*): Future[T] = {
      val remaining = new AtomicInteger(fs.length)
    
      val p = promise[T]
    
      fs foreach {
        _ onComplete {
          case s @ Success(_) => {
            if (remaining.decrementAndGet() == 0) {
              // Arbitrarily return the final success
              p tryComplete s
            }
          }
          case f @ Failure(_) => {
            p tryComplete f
          }
        }
      }
    
      p.future
    }
    
    0 讨论(0)
  • 2020-12-02 06:13

    This question has been answered but I am posting my value class solution (value classes were added in 2.10) since there isn't one here. Please feel free to criticize.

      implicit class Sugar_PimpMyFuture[T](val self: Future[T]) extends AnyVal {
        def concurrently = ConcurrentFuture(self)
      }
      case class ConcurrentFuture[A](future: Future[A]) extends AnyVal {
        def map[B](f: Future[A] => Future[B]) : ConcurrentFuture[B] = ConcurrentFuture(f(future))
        def flatMap[B](f: Future[A] => ConcurrentFuture[B]) : ConcurrentFuture[B] = concurrentFutureFlatMap(this, f) // work around no nested class in value class
      }
      def concurrentFutureFlatMap[A,B](outer: ConcurrentFuture[A], f: Future[A] => ConcurrentFuture[B]) : ConcurrentFuture[B] = {
        val p = Promise[B]()
        val inner = f(outer.future)
        inner.future onFailure { case t => p.tryFailure(t) }
        outer.future onFailure { case t => p.tryFailure(t) }
        inner.future onSuccess { case b => p.trySuccess(b) }
        ConcurrentFuture(p.future)
      }
    

    ConcurrentFuture is a no overhead Future wrapper that changes the default Future map/flatMap from do-this-then-that to combine-all-and-fail-if-any-fail. Usage:

    def func1 : Future[Int] = Future { println("f1!");throw new RuntimeException; 1 }
    def func2 : Future[String] = Future { Thread.sleep(2000);println("f2!");"f2" }
    def func3 : Future[Double] = Future { Thread.sleep(2000);println("f3!");42.0 }
    
    val f : Future[(Int,String,Double)] = {
      for {
        f1 <- func1.concurrently
        f2 <- func2.concurrently
        f3 <- func3.concurrently
      } yield for {
       v1 <- f1
       v2 <- f2
       v3 <- f3
      } yield (v1,v2,v3)
    }.future
    f.onFailure { case t => println("future failed $t") }
    

    In the example above, f1,f2 and f3 will run concurrently and if any fail in any order the future of the tuple will fail immediately.

    0 讨论(0)
  • 2020-12-02 06:15

    For this purpose I would use an Akka actor. Unlike the for-comprehension, it fails as soon as any of the futures fail, so it's a bit more efficient in that sense.

    class ResultCombiner(futs: Future[_]*) extends Actor {
    
      var origSender: ActorRef = null
      var futsRemaining: Set[Future[_]] = futs.toSet
    
      override def receive = {
        case () =>
          origSender = sender
          for(f <- futs)
            f.onComplete(result => self ! if(result.isSuccess) f else false)
        case false =>
          origSender ! SomethingFailed
        case f: Future[_] =>
          futsRemaining -= f
          if(futsRemaining.isEmpty) origSender ! EverythingSucceeded
      }
    
    }
    
    sealed trait Result
    case object SomethingFailed extends Result
    case object EverythingSucceeded extends Result
    

    Then, create the actor, send a message to it (so that it will know where to send its reply to) and wait for a reply.

    val actor = actorSystem.actorOf(Props(new ResultCombiner(f1, f2, f3)))
    try {
      val f4: Future[Result] = actor ? ()
      implicit val timeout = new Timeout(30 seconds) // or whatever
      Await.result(f4, timeout.duration).asInstanceOf[Result] match {
        case SomethingFailed => println("Oh noes!")
        case EverythingSucceeded => println("It all worked!")
      }
    } finally {
      // Avoid memory leaks: destroy the actor
      actor ! PoisonPill
    }
    
    0 讨论(0)
  • 2020-12-02 06:18

    You might want to checkout Twitter's Future API. Notably the Future.collect method. It does exactly what you want: https://twitter.github.io/scala_school/finagle.html

    The source code Future.scala is available here: https://github.com/twitter/util/blob/master/util-core/src/main/scala/com/twitter/util/Future.scala

    0 讨论(0)
  • 2020-12-02 06:19

    You can do this with futures alone. Here's one implementation. Note that it won't terminate execution early! In that case you need to do something more sophisticated (and probably implement the interruption yourself). But if you just don't want to keep waiting for something that isn't going to work, the key is to keep waiting for the first thing to finish, and stop when either nothing is left or you hit an exception:

    import scala.annotation.tailrec
    import scala.util.{Try, Success, Failure}
    import scala.concurrent._
    import scala.concurrent.duration.Duration
    import ExecutionContext.Implicits.global
    
    @tailrec def awaitSuccess[A](fs: Seq[Future[A]], done: Seq[A] = Seq()): 
    Either[Throwable, Seq[A]] = {
      val first = Future.firstCompletedOf(fs)
      Await.ready(first, Duration.Inf).value match {
        case None => awaitSuccess(fs, done)  // Shouldn't happen!
        case Some(Failure(e)) => Left(e)
        case Some(Success(_)) =>
          val (complete, running) = fs.partition(_.isCompleted)
          val answers = complete.flatMap(_.value)
          answers.find(_.isFailure) match {
            case Some(Failure(e)) => Left(e)
            case _ =>
              if (running.length > 0) awaitSuccess(running, answers.map(_.get) ++: done)
              else Right( answers.map(_.get) ++: done )
          }
      }
    }
    

    Here's an example of it in action when everything works okay:

    scala> awaitSuccess(Seq(Future{ println("Hi!") }, 
      Future{ Thread.sleep(1000); println("Fancy meeting you here!") },
      Future{ Thread.sleep(2000); println("Bye!") }
    ))
    Hi!
    Fancy meeting you here!
    Bye!
    res1: Either[Throwable,Seq[Unit]] = Right(List((), (), ()))
    

    But when something goes wrong:

    scala> awaitSuccess(Seq(Future{ println("Hi!") }, 
      Future{ Thread.sleep(1000); throw new Exception("boo"); () }, 
      Future{ Thread.sleep(2000); println("Bye!") }
    ))
    Hi!
    res2: Either[Throwable,Seq[Unit]] = Left(java.lang.Exception: boo)
    
    scala> Bye!
    
    0 讨论(0)
  • 2020-12-02 06:20

    You can use this:

    val l = List(1, 6, 8)
    
    val f = l.map{
      i => future {
        println("future " +i)
        Thread.sleep(i* 1000)
        if (i == 12)
          throw new Exception("6 is not legal.")
        i
      }
    }
    
    val f1 = Future.sequence(f)
    
    f1 onSuccess{
      case l => {
        logInfo("onSuccess")
        l.foreach(i => {
    
          logInfo("h : " + i)
    
        })
      }
    }
    
    f1 onFailure{
      case l => {
        logInfo("onFailure")
      }
    
    0 讨论(0)
提交回复
热议问题