从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 ...
随机推荐
- 自定义DZLMoneyLabel
一.简介 由于父亲生病,好久没有更新博客了,今天10.1 国庆(应该说是昨天了,已经过了12点了),心血来潮自定义了一个小label.这个控件的难度并不大,也没有什么可以值得炫耀的技术点.写这个控件的 ...
- Amoeba for MySQL MySql集群软件
一, Amoeba简述 Amoeba for MySQL致力于MySQL的分布式数据库前端代理层,它主要在应用层访问MySQL的时候充当query 路由功能,专注 分布式数据库 proxy 开发 ...
- SpringMVC接收多个对象
问题背景: 要在一个表单里同时一次性提交多名乘客的个人信息到SpringMVC,前端HTML和SpringMVC Controller里该如何处理? 第1种方法:将Json对象序列化成Json字符串提 ...
- 关于jQuery中.attr()和.prop()的问题
话说写了几句代码在ie8上能正常运行,chrome和ff却不行,朋友说这就是RP啊,郁闷! 其实功能需求是这样的,两个radio:男和女,一个button:重置.启动页面默认选中男,在用户选择女之后又 ...
- (POJ 2318)TOYS 向量叉积
题目链接:http://poj.org/problem?id=2318 #include<stdio.h> #include<cstdlib> #include<cstr ...
- java学习笔记_MIDI
import javax.sound.midi.*; public class Midi { public void play(int instrument, int note) { try { Se ...
- 类似QQ的应用毗邻(Pilin)即时聊天源码
这个应用是从安卓教程网分享过了的,个人觉得这个还是挺不错的,毗邻(Pilin)即时聊天应用源码,承诺的 基于xmpp openfire asmack 的即时聊天应用,继续完善,现在只完成了文字.表 ...
- apache和php扩展问题
1.redis扩展: windows下开发用的xampp集成的环境,想装个php-redis扩展,扩展的github地址: https://github.com/nicolasff/phpredis ...
- fltk_hello world
int main(int argc, char *argv[]) { #define WINDOW_BG FL_BLACK #define WINDOW_WIDTH 640 #define WINDO ...
- hosts文件的作用 whois查询域名信息
Whois查询域名信息 在操作系统中的路径:Window98—在Windows目录下Windows 2000/XP—在C:\WINDOWS\system32\drivers\etc目录下 内容:包 ...