如何转换CompletableFuture <stream <t>&gt;到流<t>没有阻塞

时间:2016-05-23 14:36:34

标签: asynchronous lambda java-8 java-stream completable-future

我正在使用Async Http Client library(使用Netty)向RESTful API发出异步Http Get请求。由于我想保留非阻塞行为,因此Http Get请求会返回CompletableFuture<T>的实例。因此,在RESTful API端点返回Json数组的情况下,我返回CompletableFuture<T[]>

然而,根据Erik Meijer关于The Four Essential Effects In Programming的分类,我认为Stream<T>更适合于生成异步Http Get请求并返回一个Java方法的结果Json数组。在这种情况下,我们可以看到Stream<T>Observable<T>等价物,这是返回多个值的异步计算的结果

因此,考虑到resp持有回复,我可以得到CompletableFuture<Stream<T>>如下:

 CompletableFuture<T[]> resp = …
 return resp.thenApply(Arrays::stream);

但是,我想知道如何将CompletableFuture<Stream<T>> resp转换为Stream<T>,而无需等待计算完成(即我不想阻止get()调用)?

我希望与以下表达式具有相同的结果,但在get()上没有阻止:

return resp.thenApply(Arrays::stream).get();

2 个答案:

答案 0 :(得分:4)

您可以构建一个Stream<T>来推迟调用Future<T> get()方法,就像这样:

CompletableFuture<T[]> resp = ...
return Stream
        .of(resp)                               // Stream<CompletableFuture<T[]>>
        .flatMap(f -> Arrays.stream(f.join())); // Stream<T>

为了简化使用,我使用get()代替join()来避免检查异常。

答案 1 :(得分:3)

只要异步计算的结果作为数组传递,就不能从这里获得Stream API,因为在操作数组之前,Stream操作无法启动处理元素,这意味着完成异步作业。

除非您重写异步作业以发布数组的各个元素,例如通过队列,您只能将同步推迟到流的终端操作开始时的点。换句话说,您可以在必须等待异步作业完成之前将中间操作链接到Stream。由于链接不是一项昂贵的操作,因此收益将非常小。

如果你仍然想要这样做,Miguel Gamboa’s solutionStream.of(resp).flatMap(f -> Arrays.stream(f.join()))会做,而且很简洁。不幸的是,它可能具有性能缺陷,这超过了推迟join操作的任何好处。由于数组具有可预测的长度并支持平衡分割,因此流式数组可以顺利运行,而嵌套流不仅缺少这些功能,current implementation even lacks short-circuiting processing

因此,不建议使用flatMap来推迟Stream创建,而是建议更深层次,直接支持延迟流创建:

static <T> Stream<T> getStream(CompletableFuture<T[]> resp) {
    return StreamSupport.stream(() -> Arrays.spliterator(resp.join()),
        Spliterator.ORDERED|Spliterator.SIZED|Spliterator.SUBSIZED|Spliterator.IMMUTABLE,
        false);
}

这会创建一个Stream,它会延迟join操作,直到终端操作开始,但仍然具有基于数组的Stream的性能特征。但是代码显然更复杂,如前所述,只有在提供异步操作的数组仍在运行时链接中间操作的可能性仍然存在。