转动ListenableFuture <iterable <a>&gt;到Iterable <listenablefuture <b>&gt;通过拆分和运行</listenablefuture <b> </iterable <a>

时间:2013-02-21 10:26:30

标签: java asynchronous concurrency guava future

我希望找到将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();
}

3 个答案:

答案 0 :(得分:3)

如果您将ListenableFuture视为一个框,为了从该框中获取内容,您必须阻止该主题(调用get方法)。

不确定实施splitAndRun方法是否会以某种方式使您受益。您仍然只有一个返回ListenableFuture<Iterable<A>>的异步操作。

加入Iterable<ListenableFuture<A>>作为单个ListenableFuture<Iterable<A>>的反向操作可能会有用。如果要将多个异步计算收集到一个异步计算中,可能需要使用它。

答案 1 :(得分:2)

(替代my original answer

但是,如果转型是缓慢的,或者某些输入可能会失败但是对其他输入成功会怎样?在这种情况下,我们希望单独转换每个输出。另外,我们希望确保转换只发生一次。我们的集合转换方法做出此保证。因此,在您的示例代码中,输出上的每次迭代都会向执行程序提交新任务,即使先前提交的任务可能已经完成。出于这个原因,我们建议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.transformIterables.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>>开始的必要限制。