我希望找到将ListenableFuture<Iterable<A>>
转换为单个ListenableFutures
序列的最佳方法。这是我正在寻找的方法签名:
public <A, B> Iterable<ListenableFuture<B>> splitAndRun(
final ListenableFuture<Iterable<A>> elements,
final Function<A, B> func,
final ListeningExecutorService executor
);
如果我返回ListenableFuture<Iterable<ListenableFuture<B>>>
,显然我可以这样做,但我觉得我应该能够分开并运行它并保持其异步性。
这是我到目前为止的代码,但你会注意到最后讨厌的.get()
,这会破坏事物。如果我的事情过于复杂,请原谅。
public class CallableFunction<I, O> implements Callable<O>{
private final I input;
private final Function<I, O> func;
public CallableFunction(I input, Function<I, O> func) {
this.input = input;
this.func = func;
}
@Override public O call() throws Exception {
return func.apply(input);
}
}
public <A, B> Iterable<ListenableFuture<B>> splitAndRun(
final ListenableFuture<Iterable<A>> elements,
final Function<A, B> func,
final ListeningExecutorService executor
) throws InterruptedException, ExecutionException {
return Futures.transform(elements,
new Function<Iterable<A>, Iterable<ListenableFuture<B>>>() {
@Override
public Iterable<ListenableFuture<B>> apply(Iterable<A> input) {
return Iterables.transform(input, new Function<A, ListenableFuture<B>>() {
@Override
public ListenableFuture<B> apply(A a) {
return executor.submit(new CallableFunction<A, B>(a, func));
}
});
}
}, executor).get();
}
答案 0 :(得分:3)
如果您将ListenableFuture
视为一个框,为了从该框中获取内容,您必须阻止该主题(调用get
方法)。
不确定实施splitAndRun
方法是否会以某种方式使您受益。您仍然只有一个返回ListenableFuture<Iterable<A>>
的异步操作。
加入Iterable<ListenableFuture<A>>
作为单个ListenableFuture<Iterable<A>>
的反向操作可能会有用。如果要将多个异步计算收集到一个异步计算中,可能需要使用它。
答案 1 :(得分:2)
但是,如果转型是缓慢的,或者某些输入可能会失败但是对其他输入成功会怎样?在这种情况下,我们希望单独转换每个输出。另外,我们希望确保转换只发生一次。我们的集合转换方法不做出此保证。因此,在您的示例代码中,输出上的每次迭代都会向执行程序提交新任务,即使先前提交的任务可能已经完成。出于这个原因,我们建议Iterables.transform
和朋友只在转换是轻量级的时候。 (通常情况下,如果你正在做重量级的事情,你的转换函数将抛出一个Function
不允许的检查异常。考虑这个提示:)你的例子当然不会触发提示。 )
这在代码中意味着什么?基本上,我们将颠倒我在其他答案中给出的操作顺序。我们会先从Future<Iterable<A>>
转换为Iterable<Future<A>>
。 然后我们将为每个A
提交一个单独的任务,将其转换为B
。对于后一步骤,我们将提供Executor
so that the transformation doesn't block some innocent thread。 (我们现在只需要Futures.transform
,所以我静态导入它。)
List<ListenableFuture<A>> individuals = newArrayList();
for (int i = 0; i < knownSize; i++) {
final int index = i;
individuals.add(transform(input, new Function<List<A>, A>() {
@Override
public A apply(List<A> values) {
return values.get(index);
}
}));
}
List<ListenableFuture<B>> result = newArrayList();
for (ListenableFuture<A> original : individuals) {
result.add(transform(original, function, executor));
}
return result;
无论如何,那是个主意。但我的实施是愚蠢的。我们可以轻松地同时执行这两个步骤:
List<ListenableFuture<B>> result = newArrayList();
for (int i = 0; i < knownSize; i++) {
final int index = i;
result.add(transform(input, new Function<List<A>, B>() {
@Override
public B apply(List<A> values) {
return function.apply(values.get(index));
}
}, executor));
}
return result;
因为这会使n Futures.transform
次调用而不是1,并且因为它使用单独的Executor
,所以如果转换是重量级的,那么它比我的其他解决方案更好,如果它是轻量级的则更糟。另一个警告仍然是:只有当你知道你将拥有多少输出时,这才有效。
答案 2 :(得分:1)
这有两个部分。第一种是将Future<Iterable<A>>
转换为Future<Iterable<B>>
。这可以通过嵌套Futures.transform
和Iterables.transform
来实现(如果转换速度很快 - 更多关于替代答案)。这是代码(虽然我为Iterables.transform
换了Lists.transform
,这需要Future<List<A>>
,原因我稍后会解释一下):
final ListenableFuture<List<B>> transformed =
Futures.transform(input, new Function<List<A>, List<B>>() {
@Override
public List<B> apply(List<A> inputs) {
return Lists.transform(inputs, function);
}
});
现在转换为Future<Iterable<B>>
到Iterable<Future<B>>
。正如Mairbek所说,这一般是不可能的:在知道Future
将返回多少之前,我们无法知道输出将包含多少元素。但是,如果我们知道输出中的元素数量,我们就可以使它工作。我们将应用另一个Futures.transform
。 (这就是为什么我使用上面的List
。普通Collection
也可以工作,但代码会更加丑陋。)
List<ListenableFuture<B>> result = newArrayList();
for (int i = 0; i < knownSize; i++) {
final int index = i;
result.add(Futures.transform(transformed, new Function<List<B>, B>() {
@Override
public B apply(List<B> values) {
return values.get(index);
}
}));
}
return result;
当然,列表中的每个Future
都会同时完成 - 仅在完整输入Future
完成时才会完成。也就是说,我们不能早点得到结果;我们只能以不同的格式(即单个Future
实例而不是批量列表)获取它们。这是从Future<Iterable<A>>
开始的必要限制。