ListenableFuture回调执行顺序

时间:2015-09-24 12:22:31

标签: java parallel-processing java-7 guava

Guava的ListenableFuture库提供了一种向未来任务添加回调的机制。这样做如下:

ListenableFuture<MyClass> future = myExecutor.submit(myCallable);
Futures.addCallback(future, new FutureCallback<MyClass>() {
    @Override
    public void onSuccess(@Nullable MyClass myClass) {
      doSomething(myClass);
    }

    @Override
    public void onFailure(Throwable t) {
      printWarning(t);
    }}, myCallbackExecutor);
}

您可以通过调用ListenableFuture函数等待get完成。例如:

MyClass myClass = future.get();

我的问题是,是否保证在get终止之前运行某个未来的回调。即如果在注册的许多回调执行程序中存在许多回调的未来,那么在get返回之前所有回调都会完成吗?

修改

我的用例是,我将构建器传递给许多类。每个类都填充构建器的一个字段。我希望异步填充所有字段,因为每个字段都需要外部查询来生成字段的数据。我希望呼叫我的asyncPopulateBuilder的用户收到Future,并且可以呼叫get,并确保所有字段都已填充。我想这样做的方式如下:

final Builder b;
ListenableFuture<MyClass> future = myExecutor.submit(myCallable);
Futures.addCallback(future, new FutureCallback<MyClass>() {
  @Override
  public void onSuccess(@Nullable MyClass myClass) {
    b.setMyClass(myClass);
  }

  @Override
  public void onFailure(Throwable t) {
    printWarning(t);
  }}, myCallbackExecutor);
}
// Do the same thing for all other fields.

在这种情况下填充所有字段之前,建议阻止的方法是什么?

2 个答案:

答案 0 :(得分:4)

不保证在get返回之前运行回调。更多关于以下内容。

至于如何解决这个用例,我建议将每个字段数据的查询转换为单独的Future,并将其与allAsList + transform相结合,并对此采取行动。 (我们有一天可能会提供a shortcut for the "combine" step。)

ListenableFuture<MyClass> future = myExecutor.submit(myCallable);

final ListenableFuture<Foo> foo =
    Futures.transform(
        future,
        new Function<MyClass, Foo>() { ... },
        myCallbackExecutor);
final ListenableFuture<Bar> bar = ...;
final ListenableFuture<Baz> baz = ...;

ListenableFuture<?> allAvailable = Futures.allAsList(foo, bar, baz);
ListenableFuture<?> allSet = Futures.transform(
    allAvailable, 
    new Function<Object, Object>() {
      @Override
      public Object apply(Object ignored) {
        // Use getUnchecked, since we know they already succeeded:
        builder.setFoo(Futures.getUnchecked(foo));
        builder.setFoo(Futures.getUnchecked(bar));
        builder.setFoo(Futures.getUnchecked(baz));
        return null;
      }
    }
};

现在,用户可以调用allSet.get()来等待人口。

(或许您希望allSet成为Future<Builder>,以便向用户提交对构建器的引用。或者您可能不需要全面开启{{1}完全只有一个Future,您可以使用CountDownLatch代替addCallback并在回调结束时倒计时锁定。)

这种方法也可以简化错误处理。

RE:&#34;回调是否在transform之前运行?&#34;

首先,我很确定我们不会在规范中的任何地方保证这一点,所以感谢您提出要求而不仅仅是为了它:)如果您最终想要依赖于当前实现的某些行为,请file an issue,以便我们可以添加文档和测试。

其次,如果我非常直截了当地提出您的问题,那么您所要求的内容是不可能的:如果get等待所有听众完成,那么任何调用{{1}的听众会挂起来的!

稍微宽松一点的问题是&#34;在get()返回之前,所有听众至少开始吗?&#34;事实证明这也是不可能的:假设我将两个听众连接到同一个get()以与get()一起运行。两个听众只需拨打Future并返回。其中一个听众必须先跑。当它调用directExecutor()时,它会挂起,因为第二个监听器还没有启动 - 直到第一个监听器完成时才能挂起。 (更一般地说,依赖任何给定的get()来迅速执行任务可能是危险的。)

更宽松的版本是&#34;在get()返回之前Executor是否至少为每个听众调用Future?&#34;但这最终会出现与我刚刚描述的相同情况中的问题:在submit()上调用get()会运行任务并调用submit(firstListener),这不会完成,直到第二个侦听器已启动,直到第一个侦听器完成才会发生。

如果有的话,在{/ 1>} 任何侦听器执行之前,directExecutor()将更有可能返回。但是由于线程调度的不可预测性,我们也不能依赖它。 (再说一遍:它没有记录,所以请不要依赖它,除非你要求记录它!)

答案 1 :(得分:0)

final Builder b;
CountDownLatch latch = new CountDownLatch(1);
ListenableFuture<MyClass> future = myExecutor.submit(myCallable);

Futures.addCallback(future, new FutureCallback<MyClass>() {
 @Override
 public void onSuccess(@Nullable MyClass myClass) {
 b.setMyClass(myClass);
 latch.countDown();
}
 @Override
 public void onFailure(Throwable t) {
 printWarning(t);
 latch.countDown();
}, myCallbackExecutor);

try {
        latch.await();
    } catch (InterruptedException e) {
        LOG.error("something InterruptedException", e);
    } finally {
        myCallbackExecutor.shutdown();
    }

修改

代码受@Chris Povirk启发

  

(或者您可能希望allSet成为Future,以便向用户提供对构建器的引用。或者您根本不需要完整的Future,只需一个CountDownLatch,即可在其中使用addCallback而不是转换,并在回调结束时递减锁存器。)   这种方法还可以简化错误处理。