CompletableFuture # whenComplete wird nicht aufgerufen, wenn thenApply verwendet wird
Ich habe den folgenden Code (resultierend aus meine vorherige Frage), der eine Aufgabe auf einem Remote-Server plant und dann mit @ die Fertigstellung abfraScheduledExecutorService#scheduleAtFixedRate
. Sobald die Aufgabe abgeschlossen ist, wird das Ergebnis heruntergeladen. Ich möchte ein @ zurückgebFuture
an den Anrufer, damit er entscheiden kann, wann und wie lange er blockieren soll, und ihm die Option zum Abbrechen der Aufgabe geben kann.
Mein Problem ist, dass, wenn der Client das @ stornieFuture
von @ zurückgegebdownload
Methode,whenComplete
Block wird nicht ausgeführt. Wenn ich @ entferthenApply
es tut. Es ist offensichtlich, dass ich etwas über @ missversteFuture
Zusammensetzung ... Was soll ich ändern?
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;
}
Auf der gleichen Notiz, wenn ich tue:
return completionFuture.whenComplete(...)
Anstatt vo
completionFuture.whenComplete(...);
return completionFuture;
whenComplete
wird auch nie ausgeführt. Dies scheint mir sehr intuitiv zu sein. Sollte nicht logisch dasFuture
zurückgegeben vonwhenComplete
derjenige sein, an dem ich mich festhalten sollte?
BEARBEITEN
Ich habe meinen Code geändert, um die Stornierung explizit rückgängig zu machen. Es ist abscheulich und unlesbar, aber es funktioniert und ich könnte keinen besseren Weg finden:
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;
}
EDIT 2:
Ich habe entdeckt tascalate-concurrent, eine wunderbare Bibliothek mit einer vernünftigen Implementierung vonCompletionStage
, mit Unterstützung für abhängige Versprechen (über dasDependentPromise
class), mit dem Stornierungen transparent rückgängig gemacht werden können. Scheint perfekt für diesen Anwendungsfall.
Das sollte reichen:
DependentPromise
.from(pollForCompletion(jobId))
.thenApply(this::download, true); //true means the cancellation should back-propagate
Hat diesen Ansatz nicht getestet, wohlgemerkt.