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.
在这种情况下填充所有字段之前,建议阻止的方法是什么?
答案 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而不是转换,并在回调结束时递减锁存器。) 这种方法还可以简化错误处理。