CompletableFuture#whenComplete不被调用,如果thenApply被使用 [英] CompletableFuture#whenComplete not called if thenApply is used
问题描述
我有以下代码(由我的上一个问题)在远程服务器上安排任务,然后使用 ScheduledExecutorService#scheduleAtFixedRate
轮询它完成。任务完成后,它将下载结果。
我想向调用者返回一个 Future
,以便他们可以决定阻塞的时间和长度,并给他们选择取消任务。
我的问题是,如果客户取消由下载返回的
Future
c>方法, whenComplete
块不执行。如果我删除 thenApply
它。很明显我误解了未来
组合...我应该改变什么?
public Future< Object>下载(Something something){
String jobId = schedule(something);
CompletableFuture< String> job = pollForCompletion(jobId);
return job.thenApply(this :: downloadResult);
}
private CompletableFuture< String> pollForCompletion(String jobId){
ScheduledExecutorService executor = Executors.newSingleThreadScheduledExecutor();
CompletableFuture< String> completionFuture = new CompletableFuture(<>();
ScheduledFuture<?> checkFuture = executor.scheduleAtFixedRate(() - > {
if(pollRemoteServer(jobId).equals(COMPLETE)){
completionFuture.complete(jobId);
}
},0,10,TimeUnit.SECONDS);
completionFuture
.whenComplete((result,thrown) - > {
System.out.println(XXXXXXXXXXX); //永远不会发生,除非thenApply被删除
checkFuture。 cancel(true);
executor.shutdown();
});
return completionFuture;
}
同样的注意事项,如果我这样做:
return completionFuture.whenComplete(...)
而不是
completionFuture.whenComplete
return completionFuture;
whenComplete
这似乎对我来说是违反直觉的。不应该逻辑上由
返回的
未来 whenComplete
是我应该持有吗?
EDIT:
我更改了我的代码,明确地反向传播取消。这是令人憎恶和不可读的,但它的工作,我找不到一个更好的方法:
public Future< Object&下载(Something something)throws ChartDataGenException,Exception {
String jobId = schedule(report);
CompletableFuture< String> job = pollForCompletion(jobId);
CompletableFuture< Object> result = job.thenApply(this :: download);
resulting.whenComplete((result,thrown) - > {
if(resulting.isCancelled()){//检查不是必需的,但更好地传达意图
job.cancel (true);
}
});
return result;
}
┌─────────┐
│completionFuture |
└──────────────────
↓↓
┌───────┐ ┌─────────┐┐
│whenComplete | │thenApply |
└────────────┘└─────────┘
所以当你取消 thenApply
未来时,原来的 completionFuture
保持不受影响,因为它不依赖于 thenApply
阶段。然而,如果你不链接 thenApply
阶段,你将返回原始的 completionFuture
实例并取消阶段导致所有依赖阶段取消,导致 whenComplete
动作立即执行。
但是当 thenApply
阶段取消, completionFuture
仍然可能会完成当 pollRemoteServer(jobId).equals (COMPLETE)
条件满足,因为轮询不停止。但我们不知道 jobId = schedule(something)
和 pollRemoteServer(jobId)
的关系。如果您的应用程序状态发生变化,这种情况在取消下载之后永远无法实现,那么这个未来将永远不会完成...
<关于你最后一个问题,哪个未来是我应该持有的?,没有要求有一个线性的期货链,事实上,而
CompletableFuture
使创建这样一个链很容易,更多的时候,它是最不有用的事情,因为你可以只写一个代码块,如果你有一个线性依赖。你的链接两个独立阶段的模式是正确的,但取消不能通过它,但它不会通过线性链工作。 如果你想能够取消源阶段,你需要一个引用它,但如果你想要能够得到一个依赖阶段的结果,你还需要一个引用这个阶段。
I have the following code (resulting from my previous question) that schedules a task on a remote server, and then polls it completion using ScheduledExecutorService#scheduleAtFixedRate
. Once the task is complete, it downloads the result.
I want to return a Future
to the caller so they can decide when and how long to block, and give them the option to cancel the task.
My problem is that if the client cancels the Future
returned by the download
method, whenComplete
block doesn't execute. If I remove thenApply
it does. It's obvious I'm misunderstanding something about Future
composition... What should I change?
public Future<Object> download(Something something) {
String jobId = schedule(something);
CompletableFuture<String> job = pollForCompletion(jobId);
return job.thenApply(this::downloadResult);
}
private CompletableFuture<String> pollForCompletion(String jobId) {
ScheduledExecutorService executor = Executors.newSingleThreadScheduledExecutor();
CompletableFuture<String> completionFuture = new CompletableFuture<>();
ScheduledFuture<?> checkFuture = executor.scheduleAtFixedRate(() -> {
if (pollRemoteServer(jobId).equals("COMPLETE")) {
completionFuture.complete(jobId);
}
}, 0, 10, TimeUnit.SECONDS);
completionFuture
.whenComplete((result, thrown) -> {
System.out.println("XXXXXXXXXXX"); //Never happens unless thenApply is removed
checkFuture.cancel(true);
executor.shutdown();
});
return completionFuture;
}
On the same note, if I do:
return completionFuture.whenComplete(...)
instead of
completionFuture.whenComplete(...);
return completionFuture;
whenComplete
also never executes. This seems very counterintuitive to me. Shouldn't logically the Future
returned by whenComplete
be the one I should hold on to?
EDIT:
I changed my code to explicitly back-propagate the cancellation. It's abhorrent and unreadable, but it works and I couldn't find a better way:
public Future<Object> download(Something something) throws ChartDataGenException, Exception {
String jobId = schedule(report);
CompletableFuture<String> job = pollForCompletion(jobId);
CompletableFuture<Object> resulting = job.thenApply(this::download);
resulting.whenComplete((result, thrown) -> {
if (resulting.isCancelled()) { //the check is not necessary, but communicates the intent better
job.cancel(true);
}
});
return resulting;
}
Your structure is as follows:
┌──────────────────┐
│ completionFuture |
└──────────────────┘
↓ ↓
┌──────────────┐ ┌───────────┐
│ whenComplete | │ thenApply |
└──────────────┘ └───────────┘
So when you cancel the thenApply
future, the original completionFuture
object remains unaffected as it doesn’t depend on the thenApply
stage. If, however, you don’t chain the thenApply
stage, you’re returning the original completionFuture
instance and canceling this stage causes the cancellation of all dependent stages, causing the whenComplete
action to be executed immediately.
But when the thenApply
stage is cancelled, the completionFuture
still may get completed when the pollRemoteServer(jobId).equals("COMPLETE")
condition is fulfilled, as that polling doesn’t stop. But we don’t know the relationship of jobId = schedule(something)
and pollRemoteServer(jobId)
. If your application state changes in a way that this condition can never be fulfilled after canceling a download, this future will never complete…
Regarding your last question, which future is "the one I should hold on to?", there is no requirement to have a linear chain of futures, in fact, while the convenience methods of CompletableFuture
make it easy to create such a chain, more than often, it’s the least useful thing to do, as you could just write a block of code, if you have a linear dependency. Your model of chaining two independent stages is right, but cancellation doesn’t work through it, but it wouldn’t work through a linear chain either.
If you want to be able to cancel the source stage, you need a reference to it, but if you want to be able to get the result of a dependent stage, you’ll need a reference to that stage too.
这篇关于CompletableFuture#whenComplete不被调用,如果thenApply被使用的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!