我有以下代码(由my previous question生成),用于在远程服务器上调度任务,然后使用ScheduledExecutorService#scheduleAtFixedRate
轮询完成。任务完成后,会下载结果。
我想将Future
返回给调用者,以便他们可以决定阻止的时间和时间,并为他们提供取消任务的选项。
我的问题是,如果客户端取消Future
方法返回的download
,则whenComplete
块不会执行。如果我删除它thenApply
。很明显,我误解了Future
作文......我该怎么改变?
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;
}
在同一张纸条上,如果我这样做:
return completionFuture.whenComplete(...)
而不是
completionFuture.whenComplete(...);
return completionFuture;
whenComplete
也永远不会执行。这对我来说似乎非常违反直觉。逻辑上,Future
返回的whenComplete
不应该是我应该保留的那个吗?
修改
我将代码更改为显式反向传播取消。这是令人憎恶和难以理解的,但它有效,我找不到更好的方法:
public Future<Object> download(Something something) throws ChartDataGenException, Exception {
String jobId = schedule(something);
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;
}
编辑2:
我发现了tascalate-concurrent,一个很棒的库提供了CompletionStage
的理智实现,支持依赖的promises(通过DependentPromise
类),它可以透明地反向传播取消。对于这个用例来说似乎很完美。
这应该足够了:
DependentPromise
.from(pollForCompletion(jobId))
.thenApply(this::download, true); //true means the cancellation should back-propagate
请注意,不要测试这种方法。
答案 0 :(得分:5)
您的结构如下:
┌──────────────────┐
│ completionFuture |
└──────────────────┘
↓ ↓
┌──────────────┐ ┌───────────┐
│ whenComplete | │ thenApply |
└──────────────┘ └───────────┘
因此,当您取消thenApply
未来时,原始completionFuture
对象不会受到影响,因为它不依赖于thenApply
阶段。但是,如果您没有链接thenApply
阶段,则返回原始completionFuture
实例并取消此阶段会导致取消所有相关阶段,从而导致whenComplete
操作立即执行。
但是当thenApply
阶段被取消时,completionFuture
仍然可以在pollRemoteServer(jobId).equals("COMPLETE")
条件满足时完成,因为该轮询不会停止。但我们不知道jobId = schedule(something)
和pollRemoteServer(jobId)
的关系。如果您的应用程序状态发生变化,以至于在取消下载后永远无法满足此条件,那么这个未来永远不会完成......
关于你的最后一个问题,哪个未来是“我应该坚持的?”,事实上,没有要求有一个线性的期货链,而CompletableFuture
的便利方法使它变得容易要创建这样一个链,通常情况下,这是最不实用的事情,因为如果你有一个线性依赖,你可以写一个代码块。你链接两个独立阶段的模型是正确的,但取消并不适用于它,但它也无法通过线性链工作。
如果您希望能够取消源阶段,则需要对其进行引用,但如果您希望能够获得依赖阶段的结果,则还需要对该阶段的引用。 / p>