【问题标题】:Turning a ListenableFuture<Iterable<A>> to Iterable<ListenableFuture<B>> through split and run通过拆分和运行将 ListenableFuture<Iterable<A>> 变为 Iterable<ListenableFuture<B>>
【发布时间】:2013-02-21 10:26:30
【问题描述】:

我正在寻找将ListenableFuture&lt;Iterable&lt;A&gt;&gt; 转换为单个ListenableFutures 序列的最佳方法。这是我正在寻找的方法签名:

public <A, B> Iterable<ListenableFuture<B>> splitAndRun(
    final ListenableFuture<Iterable<A>> elements, 
    final Function<A, B> func, 
    final ListeningExecutorService executor
);

如果我返回 ListenableFuture&lt;Iterable&lt;ListenableFuture&lt;B&gt;&gt;&gt; 显然我可以这样做,但我觉得我应该能够拆分并运行它并保持它的异步性。

这是我目前的代码,但你会注意到结尾处的讨厌的.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();
}

【问题讨论】:

  • 将 Chris Povirk 的第二个答案视为您问题的解决方案。通过一些调整,它只会导致单个get 等待合并的未来。使用 Mairbek Khadikov 的回答,您的代码中至少需要有 2 个gets。无论如何,为问题 +1。

标签: java asynchronous concurrency guava future


【解决方案1】:

如果您将ListenableFuture 视为一个盒子,为了从该盒子中获取某些内容,您必须阻塞线程(调用get 方法)。

不确定实现splitAndRun 方法是否会对您有所帮助。您仍然会有一个返回 ListenableFuture&lt;Iterable&lt;A&gt;&gt; 的异步操作。

不过,将Iterable&lt;ListenableFuture&lt;A&gt;&gt; 作为单个ListenableFuture&lt;Iterable&lt;A&gt;&gt; 加入的反向操作可能很有用。如果要将多个异步计算收集到一个中,则可能需要使用它。

【讨论】:

  • 对于反向操作,那将是Futures.allAsList()
  • 是的,这很公平。我希望谷歌可能有一些适用于期货的聪明的集合,并会根据它们的完成状态对它们进行迭代。不幸的是,那会很可怕。
【解决方案2】:

(替代my original answer

但是,如果转换速度很慢,或者某些输入可能失败但其他输入成功,该怎么办?在这种情况下,我们希望单独转换每个输出。另外,我们希望确保转换只发生一次。我们的集合转换方法做出此保证。因此,在您的示例代码中,对输出的每次迭代都会向执行程序提交新任务,即使之前提交的任务可能已经完成。正因如此,我们推荐Iterables.transform和朋友们只在改造轻量级的时候使用。 (通常,如果你正在做一些重量级的事情,你的转换函数会抛出一个检查异常,Function 不允许。考虑这是一个提示 :) 当然,你的例子恰好不会触发提示。)

这在代码中意味着什么?基本上,我们将颠倒我在其他答案中给出的操作顺序。我们将首先从Future&lt;Iterable&lt;A&gt;&gt; 转换为Iterable&lt;Future&lt;A&gt;&gt;然后我们将为每个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,所以如果转换是重量级的,它比我的其他解决方案要好,如果它是轻量级的则更糟。另一个需要注意的是:只有当您知道您将拥有多少输出时,这才有效。

【讨论】:

  • 这是一个很好的解决方案,因为它没有明确阻塞。不幸的是,在实践中它与我的原始实现没有什么不同,因为获取列表的第一个元素有一个隐含的块。这不会使解决方案变得糟糕或错误,我喜欢它,但它必须等待初始列表可用。
  • 实际上,考虑一下,这与您得到“正确”答案的距离很近,所以我不妨将其标记为这样。以后可能发生的隐含块比现在必须发生的隐含块更好。
  • ...虽然我确实错过了潜入其中的“knownSize”;)
  • 是的,我想在我不太受欢迎的答案中提到它之后,我可以把它偷偷溜进来:stackoverflow.com/a/15071244/28465 :) 正如我在那里指出的那样,如果你没有knownSize,那么你'运气不好。
【解决方案3】:

这有两个部分。第一个是将Future&lt;Iterable&lt;A&gt;&gt; 转换为Future&lt;Iterable&lt;B&gt;&gt;。这可以通过嵌套Futures.transformIterables.transform 来实现(如果 转换速度很快——在替代答案中有更多信息)。这是代码(尽管我已经将Iterables.transform 换成了Lists.transform,这需要Future&lt;List&lt;A&gt;&gt;,原因我稍后也会解释):

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&lt;Iterable&lt;B&gt;&gt;Iterable&lt;Future&lt;B&gt;&gt; 的转换。正如 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&lt;Iterable&lt;A&gt;&gt; 开头的必要限制。

【讨论】:

    猜你喜欢
    • 2012-10-18
    • 2012-01-04
    • 1970-01-01
    • 1970-01-01
    • 2016-12-09
    • 2020-05-07
    • 1970-01-01
    • 2010-11-07
    • 2021-08-01
    相关资源
    最近更新 更多