【问题标题】:Why Future.sequence executes my futures in parallel rather than in series?为什么 Future.sequence 并行而不是串行执行我的期货?
【发布时间】:2014-09-23 06:48:09
【问题描述】:

序列”一词的意思是一系列一个接一个的动作。

object Test {

  def main(args: Array[String]) {

    def producer() = {
      val list = Seq(
          future { println("startFirst"); Thread.sleep(3000); println("stopFirst") }, 
          future { println("startSecond"); Thread.sleep(1000); println("stopSecond") }
      )
      Future.sequence(list)
    }

   Await.result(producer, Duration.Inf)
  }
}

因此,我希望这个程序能够打印: startFirst stopFirst startSecond stopSecond

甚至: startSecond stopSecond startFirst stopFirst

但不是(碰巧): startFirst startSecond stopSecond stopFirst

为什么这个方法不叫Future.parallel()? 我应该用什么来保证Seq 期货中的所有期货都是串行触发的(而不是并行)?

【问题讨论】:

  • 这是一个结果序列,而不是效果序列
  • 另外,我猜这个名字来自于单子序列。
  • 你也可以用这段代码stackoverflow.com/a/41657239完成结果

标签: scala future


【解决方案1】:

期货是同时运行的,因为它们是同时启动的 :)。 要按顺序运行它们,您需要使用 flatMap:

Future { println("startFirst"); 
         Thread.sleep(3000); 
         println("stopFirst") 
        }.flatMap{
         _ =>  Future { 
                       println("startSecond"); 
                       Thread.sleep(1000); 
                       println("stopSecond") 
               }
        }

Future.sequence 只是变成Seq[Future[T]] => Future[Seq[T]],这意味着收集所有已经开始的期货的结果并将其放在未来。

【讨论】:

  • 序列是 M[Future[A]] => Future[M[A]]
【解决方案2】:

对原来的 Future.sequence 稍加改动,就会使未来的执行序列化:

def seq[A, M[X] <: TraversableOnce[X]](in: M[() => Future[A]])(implicit cbf: CanBuildFrom[M[()=>Future[A]], A, M[A]], executor: ExecutionContext): Future[M[A]] = {
    in.foldLeft(Future.successful(cbf(in))) {
       (fr, ffa) => for (r <- fr; a <- ffa()) yield (r += a)
    } map (_.result())
}

您的代码将如下所示:

object Test {
def main(args: Array[String]) {

    def producer() = {
      val list = Seq(
          {() => future { println("startFirst"); Thread.sleep(3000); println("stopFirst") }}, 
          {() => future { println("startSecond"); Thread.sleep(1000); println("stopSecond") }}
      )
      FutureExt.seq(list)
    }

    Await.result(producer, Duration.Inf)
    }
}

这与您的原始代码非常相似,并且与原始 Future.sequence() 具有相同的结果集合

【讨论】:

    【解决方案3】:

    线性化:

    import scala.concurrent._
    import scala.collection.mutable.Builder
    import scala.collection.generic.CanBuildFrom
    import language.higherKinds
    
    /**
     * Linearize asynchronously applies a given function in-order to a sequence of values, producing a Future with the result of the function applications.
     * Execution of subsequent entries will be aborted if an exception is thrown in the application of the function.
     */
    def linearize[T, U, C[T] <: Traversable[T]](s: C[T])(f: T => U)(implicit cbf: CanBuildFrom[C[T], U, C[U]], e: ExecutionContext): Future[C[U]] = {
      def next(i: Iterator[T], b: Builder[U, C[U]]): Future[C[U]] = if(!i.hasNext) Future successful b.result else Future { b += f(i.next()) } flatMap { b => next(i, b) }
      next(s.toIterator, cbf(s))
    }
    
    scala> linearize(1 to 100)(_.toString) foreach println
    
    scala> Vector(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100)
    

    发件人:https://gist.github.com/viktorklang/3347939

    【讨论】:

      【解决方案4】:

      如果您有一些值的序列,并且您想将它们映射到Future 并依次执行它们:

      import scala.concurrent.ExecutionContext.Implicits.global
      
      implicit class SeqExtension[A](s: Seq[A]) {
        def foldLeftToFuture[B](initial: B)(f: (B, A) => Future[B])(implicit ec: ExecutionContext): Future[B] =
          s.foldLeft(Future(initial))((future, item) => future.flatMap(f(_, item)))
      
        def mapInSeries[B](f: A => Future[B])(implicit ec: ExecutionContext): Future[Seq[B]] =
          s.foldLeftToFuture(Seq[B]())((seq, item) => f(item).map(seq :+ _))
      }
      
      val stringsFuture: Future[Seq[String]] = Seq(1, 2, 3).mapInSeries[String](i => Future(i.toString))
      val strings = Await.result(stringsFuture, Duration.Inf) // List("1", "2", "3")
      

      【讨论】:

        【解决方案5】:

        或者您可以使用 for {..} yield 块来获取 Future 的序列。 Future.sequence 将 Seq[Future] 转换为 Future[Seq]

        【讨论】:

        • for..yield 的问题在于方法是我可以一个一个地插入 for 块期货,但我有一个序列!
        • 或许你可以试试 map()。未来很少有供应商特定的实现(typesafe,twitter),我想你可能想要在 twitter scala 学校提到一个叫做“concurrent composiiton”twitter.github.io/scala_school/finagle.html#futconcurrent
        • 注意:for comprehension 中的 future 仅当它们也在 for comprehension 中定义时才会按顺序执行。如果您使用已经定义的期货,则 for 理解对顺序没有帮助。
        • 一旦你理解了 for 理解,+1 IMO 的理解在语法上比平面地图更清晰。
        猜你喜欢
        • 2015-03-10
        • 1970-01-01
        • 2010-11-27
        • 2012-02-25
        • 2014-03-11
        • 2014-08-08
        • 1970-01-01
        • 1970-01-01
        • 2011-08-15
        相关资源
        最近更新 更多