从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
}
}
从List[Future[T]]到Future[List[T]]的更多相关文章
- 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 ...
- Future 异步回调 大起底之 Java Future 与 Guava Future
目录 写在前面 1. Future模式异步回调大起底 1.1. 从泡茶的案例说起 1.2. 何为异步回调 1.2.1. 同步.异步.阻塞.非阻塞 1.2.2. 阻塞模式的泡茶案例图解 1.2.3. 回 ...
- <2013 07 06> Future and Near Future
试图了解 量子力学 近现代基础物理学理论 量子计算机 脑科学 近现代生物学 遗传变异与进化 复杂工程学 系统工程 管理科学 人工智能 智能算法 机器学习 深度学习 大数据 云计算 ...
- 线程笔记:Future模式
线程技术可以让我们的程序同时做多件事情,线程的工作模式有很多,常见的一种模式就是处理网站的并发,今天我来说说线程另一种很常见的模式,这个模式和前端里的ajax类似:浏览器一个主线程执行javascri ...
- java多线程系类:JUC线程池:06之Callable和Future(转)
概要 本章介绍线程池中的Callable和Future.Callable 和 Future 简介示例和源码分析(基于JDK1.7.0_40) 转载请注明出处:http://www.cnblogs.co ...
- Java并发编程:Callable、Future和FutureTask
作者:海子 出处:http://www.cnblogs.com/dolphin0520/ 本博客中未标明转载的文章归作者海子和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置 ...
- Scalaz(44)- concurrency :scalaz Future,尚不完整的多线程类型
scala已经配备了自身的Future类.我们先举个例子来了解scala Future的具体操作: import scala.concurrent._ import ExecutionContext. ...
- Java多线程与并发库高级应用-Callable与Future的应用
Callable这种任务可以返回结果,返回的结果可以由Future去拿 >Future取得的结果类型和Callable返回的结果类型必须一致,这是通过泛型来实现的. >Completion ...
- Callable 和 Future接口 学习
* Callable是类似于Runnable的接口,实现Callable接口的类和实现Runnable的类都是可被其它线程执行的任务. * Callable和Runnable有几点不同: * (1)C ...
随机推荐
- AIDL进程间调用与Binder的简单介绍
Binder是安卓中特有的一种进程间通信(IPC)方式,从Unix发展而来的手段,通信双方必须处理线程同步.内存管理等复杂问题,传统的Socket.匿名通道(Pipe).匿名管道(FIFO).信号量( ...
- 第一篇:groovy对DSL的语法支持
引子 我们用一段gradle的脚本做引子,理解这一段脚本与一般的groovy代码是怎么联系起来的 buildscript { repositories { jcenter() mavenLocal() ...
- Cocos2d-x开发实例:单点触摸事件
下面我们通过一个实例详细了解一下,层中单点触摸事件的实现过程.感受一下它的缺点和优点.该实例场景如下图所示,场景中有两个方块精灵,我们可以点击和移动它们. 下面我们看看HelloWorldScen ...
- PHP中常量
PHP中常量 常量就是一种特殊的变量,PHP中的常量值一旦定义,在程序运行过程中不可更改,常量本身也不允许删除. 程序是用于解决现实问题,由两部分组成:代码,数据 常量的定义: 语法1: define ...
- [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 ...
- 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 ...
- [设计模式]NetworkManagementService中的观察者模式
观察者模式 观察者模式有如下角色 (1)被观察者(Subject) (2)观察者(Observer) public class Subject{ private: list<Observer&g ...
- 实现cookie跨域访问
需求:A系统(www.a.com)里设置一个浏览器cookie,B系统(www.b.com)需要能够访问到A设置的cookie. 通过HTML SCRIPT标签跨域写cookie: 由于html的sc ...
- 浅谈Javascript闭包
垃圾回收器 我个人把闭包抽象的称之为”阻止垃圾回收器的函数”或者”有权访问另一个函数内部变量的函数"(当然这个是我个人的理解方式,每个人可能会有不同的理解方式),为什么这样说?这样说还得说说 ...
- mapper配置
一:查询 <?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapper PUBLIC &q ...