从List[Future[T]]到Future[List[T]]

在课程<Principles Of Reactive Programming>里Week3的一节 "Promises, promises, promises"中,Erik Meijer举了一个例子,实现一个函数:

def sequence[T](fs: List[Future[T]]): Future[List[T]] = {.....}

这个函数实际在Scala library的Future对象中有标准的实现。

def  sequence[A, M[X] <: TraversableOnce[X]](in: M[Future[A]])(implicit cbf: CanBuildFrom[M[Future[A]], A, M[A]], executor: ExecutionContext): Future[M[A]]

 

Simple version of Future.traverse. Transforms a TraversableOnce[Future[A]] into a Future[TraversableOnce[A]]. Useful for reducing many Futures into a single Future.

俺就想试着自己实现一下,于是写出了下面这段有问题的代码……

object ConcurrentTool {
  def collect[T](futures: List[Future[T]]): Future[List[T]] = {
    val p = Promise[List[T]]
    var list = List.empty[T]
    futures.foldLeft(list) {
      (curr, future) => {
        if (p.isCompleted)
          curr
        else {
          future.onComplete {
            case Failure(e) => p.failure(e)
            case Success(e) => list = e :: list
          }
          list
        }
      }
    }
    if (!p.isCompleted)
      p.success(list)
    p.future
  }

def main(args: Array[String]) = {
    def futureOne = {
      Future {
        1
      }
    }
    def futureTwo = {
      Future {
        2
      }
    }

    val collection = collect(List(futureOne, futureTwo))
    val lists = Await.result(collection, 1 seconds)
    println(lists.size)

  }
}

collect方法用于将一个List[Future[T]]变成一个Future[List[T]]。然后我传给它两个future,等返回的Future[List[T]] compelete,然后取结果List的大小,打印出来。

但是结果是0……但是,在我加了断点进行调试时,有时结果是2,为啥呢? 错误不可怕,这是纠正自己的机会。

再来看一下collect方法的实现吧。我用一个Promise生成最后的future, 用一个空List做为foldLeft的初始值,然后遍历List[Future]里的所有future。对于每个future,给它注册一个回调函数,当它fail的时候,用引起fail的异常去complete跟最后结果相关的那个Promise,如果这个future成功了,就把它的结果附加在list里。在注册完回调之后,我返回保存结果的list。

问题就在这些操作的执行时间上。给future注册回调函数的动作是在main线程中,这个注册不会阻塞main线程的执行,假如被注册的函数的确是在另一个线程中执行的,那么在注册完回调函数之后,我返回的list仍然可能是最初的那个empty list, 所以在foldLeft完成后,foldLeft返回的仍然可能是那个empty list。接下来,我判断p.isCompleted, 如果否,我就用这个list去complete这个Promise,实际上我用一个empty list去complete了它,所以在获取collection的结果后,发现这个是一个空列表。

那么我们想要的结果如何实现呢?关键是,必须得生成这个Future[List]的所有future都compelete时,这个Future[List]才能complete。如何实现这一点呢?

  • 我们可以阻塞执行collect方法的线程,显式地等待List[Future]里的所有future完成,再complete跟结果相关的那个Promise。但是这样做,会阻塞调用collect的线程,也违背了我们返回一个Future的目的。
  • 我们可以在另一个线程里等待List[Future]里的所有future完成,再complete跟结果相关的那个Promise。在collect方法中,返回Promise对应的future。这样就不会阻塞调用collect的线程。

如何在另一个线程等待呢?可以用Await来阻塞等待,或者注册callback,使得当所有future完成时,callback被调用。

假如有两个Future,可以用下边的代码注册callback。

 def waitBoth[T](futureA: Future[T], futureB: Future[T]): Future[List[T]] = {
    val p = Promise[List[T]]()
    futureA.onComplete{
      case Failure(e) => p.failure(e)
      case Success(t) => futureB.onComplete{
        case Failure(eb) => p.failure(eb)
        case Success(b) => p.success(t :: b :: Nil)
      }
    }
    p.future
  }

这个变形一下,用flatMap和map表示就是

 def waitBoth[T](futureA: Future[T], futureB: Future[T]): Future[List[T]] = {
    val p = Promise[List[T]]()
    futureA.onComplete{
      case Failure(e) => p.failure(e)
      case Success(t) => futureA.flatMap{a: T =>
        futureB map {b =>
          p.success(a :: b :: Nil)
        }
      }
    }
    p.future
  }

实际上Future的map和flatMap在实现时也用了Promise, 上边的代码简化一下就是

  def waitBoth[T](futureA: Future[T], futureB: Future[T]): Future[List[T]] = {
    futureA.flatMap { a: T =>
      futureB map { b =>
        a :: b :: Nil
      }
    }
  }

再把flatMap和map转成for循环表示,就是

  def waitTwo[T](futureA: Future[T], futureB: Future[T]): Future[List[T]] = {
    for {
      a <- futureA
      b <- futureB
    } yield a :: b :: Nil
  }

那么如何组合更多的Future呢?我们来写一个方法把一个Future[T]的结果附加到Future[List[T]]中

  def waitMore[T](futureA: Future[T], futures: Future[List[T]]): Future[List[T]] = {
    for{
      a <- futureA
      b <- futures
    }yield a :: b
  }

然后以此为基础,就可以构造最早提到的sequence函数

  def waitSome[T](futures: List[Future[T]]): Future[List[T]] = {
    val p = Promise[List[T]]()
    p.success(Nil)
    val init: Future[List[T]] = p.future
    futures.foldLeft(init){
      (curr, f) => waitMore(f, curr)
    }
  }

 上边代码关键在于了解到我们需要一个Future作为foldLeft的初始值,它必须是success的,且使其success的值为Nil。这个init实际上也可以用 val init: Future[List[T]] = Future{Nil}来得到。

这种形式距离<Principles Of Reactive Programming>给出的答案已经很接近了。实际上Erik Meijer给出了两个解法,其中跟这个相近的是

  def sequence[T](fs: List[Future[T]]): Future[List[T]] = {
    val successful = Promise[List[T]]
    successful.success(Nil)
    fs.foldRight(successful.future){
      (f, acc) => for{x <-f; xs <- acc} yield x :: xs
    }
  }

 

posted @ 2014-11-25 14:19  devos  阅读(1623)  评论(0编辑  收藏  举报