在课程<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. sql中nvarchar(max)长度测试

    nvarchar(max)长度测试:在使用convert强制类型转化之后 文本长度可以突破8000的上限.并且nvarchar(max)的最大长度可达到2^31以下为验证SQL: Declare @A ...

  2. asp.net中Repeater控件用法笔记

    大家可能都对datagrid比较熟悉,但是如果在数据量大的时候,我们就得考虑使用 repeater作为我们的数据绑定控件了.Repeater控件与DataGrid (以及DataList)控件的主要区 ...

  3. Cocos移植到Android-Android.mk编译文件

    我们在上一篇博客中年使用的cocos工具对于C和C++源代码进行编译.事实上cocos工具读取<游戏工程目录>\proj.android\jni\目录中的Android.mk文件,进行交叉 ...

  4. iOS开发——毛玻璃透明

    主要实现的代码如下: self.rateInfoView是定义好的控制属性控件 可以改变透明度的值来改变毛玻璃透明的效果 // 虚拟交易费率弹窗 - (void)showRateInfo{ self. ...

  5. JqGrid在IE8中表头不能分组的解决办法

    修改JqGrid的js脚本: for (d = 0; d < c; d++) { if (b[d] != undefined) { //主要是添加这个判断 if (b[d].startColum ...

  6. Html.ActionLink 几种重载方式说明及例子

    本文整理了该方法的几种重载形式:一 Html.ActionLink("linkText","actionName")该重载的第一个参数是该链接要显示的文字,第二 ...

  7. Android - 代码片段

    转载说明 本篇文章可能已经更新,最新文章请转:http://www.sollyu.com/android-code-snippets/ 说明 此篇文章为个人日常使用所整理的一此代码片段,此篇文正将会不 ...

  8. Java常见知识问答

    1.String.StringBuilder.StringBuffer (1).String是字符串常量,不允许改变 (2).StringBuffer先开辟了一块空间,可以允许改变,即向这个空间添加值 ...

  9. 上传图片(基于zepto.js)

    效果如下: <div class="otherPic"> <div id="showOtherImage"></div> & ...

  10. Swing组件Jtree,JTablePane选项卡运用

    今天开始写技术博客,说实话,本没有什么技术,说是总结也好,说是分享也罢,总之是想自己有意识的做一些事情,作为一名即将毕业的大学生,总是想以最好的状态,去面向社会,今天就是我准备好了的时候,本人将技术博 ...