在课程<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
}
}

从List[Future[T]]到Future[List[T]]的更多相关文章

  1. Transform java future into completable future 【将 future 转成 completable future】

    Future is introduced in JDK 1.5 by Doug Lea to represent "the result of an asynchronous computa ...

  2. Future 异步回调 大起底之 Java Future 与 Guava Future

    目录 写在前面 1. Future模式异步回调大起底 1.1. 从泡茶的案例说起 1.2. 何为异步回调 1.2.1. 同步.异步.阻塞.非阻塞 1.2.2. 阻塞模式的泡茶案例图解 1.2.3. 回 ...

  3. <2013 07 06> Future and Near Future

    试图了解     量子力学 近现代基础物理学理论 量子计算机   脑科学 近现代生物学 遗传变异与进化   复杂工程学 系统工程 管理科学   人工智能 智能算法 机器学习 深度学习 大数据 云计算 ...

  4. 线程笔记:Future模式

    线程技术可以让我们的程序同时做多件事情,线程的工作模式有很多,常见的一种模式就是处理网站的并发,今天我来说说线程另一种很常见的模式,这个模式和前端里的ajax类似:浏览器一个主线程执行javascri ...

  5. java多线程系类:JUC线程池:06之Callable和Future(转)

    概要 本章介绍线程池中的Callable和Future.Callable 和 Future 简介示例和源码分析(基于JDK1.7.0_40) 转载请注明出处:http://www.cnblogs.co ...

  6. Java并发编程:Callable、Future和FutureTask

    作者:海子 出处:http://www.cnblogs.com/dolphin0520/ 本博客中未标明转载的文章归作者海子和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置 ...

  7. Scalaz(44)- concurrency :scalaz Future,尚不完整的多线程类型

    scala已经配备了自身的Future类.我们先举个例子来了解scala Future的具体操作: import scala.concurrent._ import ExecutionContext. ...

  8. Java多线程与并发库高级应用-Callable与Future的应用

    Callable这种任务可以返回结果,返回的结果可以由Future去拿 >Future取得的结果类型和Callable返回的结果类型必须一致,这是通过泛型来实现的. >Completion ...

  9. Callable 和 Future接口 学习

    * Callable是类似于Runnable的接口,实现Callable接口的类和实现Runnable的类都是可被其它线程执行的任务. * Callable和Runnable有几点不同: * (1)C ...

随机推荐

  1. AIDL进程间调用与Binder的简单介绍

    Binder是安卓中特有的一种进程间通信(IPC)方式,从Unix发展而来的手段,通信双方必须处理线程同步.内存管理等复杂问题,传统的Socket.匿名通道(Pipe).匿名管道(FIFO).信号量( ...

  2. 第一篇:groovy对DSL的语法支持

    引子 我们用一段gradle的脚本做引子,理解这一段脚本与一般的groovy代码是怎么联系起来的 buildscript { repositories { jcenter() mavenLocal() ...

  3. Cocos2d-x开发实例:单点触摸事件

    下面我们通过一个实例详细了解一下,层中单点触摸事件的实现过程.感受一下它的缺点和优点.该实例场景如下图所示,场景中有两个方块精灵,我们可以点击和移动它们.   下面我们看看HelloWorldScen ...

  4. PHP中常量

    PHP中常量 常量就是一种特殊的变量,PHP中的常量值一旦定义,在程序运行过程中不可更改,常量本身也不允许删除. 程序是用于解决现实问题,由两部分组成:代码,数据 常量的定义: 语法1: define ...

  5. [zz] pgpool-II load balancing from FAQ

    It seems my pgpool-II does not do load balancing. Why? First of all, pgpool-II' load balancing is &q ...

  6. From MSI to WiX, Part 4 - Features and Components by Alex Shevchuk

    Following content is directly reprinted from : http://blogs.technet.com/b/alexshev/archive/2008/08/2 ...

  7. [设计模式]NetworkManagementService中的观察者模式

    观察者模式 观察者模式有如下角色 (1)被观察者(Subject) (2)观察者(Observer) public class Subject{ private: list<Observer&g ...

  8. 实现cookie跨域访问

    需求:A系统(www.a.com)里设置一个浏览器cookie,B系统(www.b.com)需要能够访问到A设置的cookie. 通过HTML SCRIPT标签跨域写cookie: 由于html的sc ...

  9. 浅谈Javascript闭包

    垃圾回收器 我个人把闭包抽象的称之为”阻止垃圾回收器的函数”或者”有权访问另一个函数内部变量的函数"(当然这个是我个人的理解方式,每个人可能会有不同的理解方式),为什么这样说?这样说还得说说 ...

  10. mapper配置

    一:查询 <?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapper PUBLIC &q ...