Стоит сначала указать, как работает CompletableFuture
(или почему он назван так, как он есть):
CompletableFuture<?> f = CompletableFuture.supplyAsync(supplier, executionService);
в основном эквивалентно
CompletableFuture<?> f = new CompletableFuture<>();
executionService.execute(() -> {
if(!f.isDone()) {
try {
f.complete(supplier.get());
}
catch(Throwable t) {
f.completeExceptionally(t);
}
}
});
Нет подключение от CompletableFuture
к коду, выполняемому Executor
, фактически, мы можем иметь произвольное количество текущих попыток завершения. Тот факт, что конкретный код предназначен для завершения экземпляра CompletableFuture
, становится очевидным только при вызове одного из методов завершения.
Следовательно, CompletableFuture
никак не может повлиять на выполняемую операцию, это включает прерывание при отмене или подобное. Как документация CompletableFuture
гласит:
Метод Отмена имеет тот же эффект, что и completeExceptionally(new CancellationException())
Так отмена - это просто еще одна попытка завершения, которая выиграет, если она будет первой, но не повлияет на любую другую попытку завершения.
Так что orTimeout(long timeout, TimeUnit unit)
не сильно отличается в этом отношении. По истечении времени ожидания он выполнит эквивалент completeExceptionally(new TimeoutException())
, который выиграет, если никакая другая попытка завершения не была быстрее, что повлияет на зависимые этапы, но не на другие текущие попытки завершения, например, которые expensiveService.processAndGet()
инициировали в вашем случае.
Вы можете выполнить желаемую операцию, например
CompletableFuture<Upload> future = expensiveService.processAndGet();
CompletableFuture<Upload> alternative = CompletableFuture.supplyAsync(
() -> resultService.get(processId), CompletableFuture.delayedExecutor(2, TimeUnit.SECONDS));
return future.applyToEither(alternative, Function.identity())
.whenComplete((u,t) -> alternative.cancel(false));
. С delayedExecutor
мы используем то же средство, что и orTimeout
и completeOnTimeout
. Он не оценивает указанное Supplier
до указанного времени или не выполняет его вообще, если отмена в future.whenComplete
выполняется быстрее. applyToEither
обеспечит, какой бы результат не был получен быстрее.
Это не завершает future
по таймауту, но, как уже было сказано, его завершение не повлияет на исходные вычисления, так что это также будет работать :
CompletableFuture<Upload> future = expensiveService.processAndGet();
CompletableFuture.delayedExecutor(2, TimeUnit.SECONDS)
.execute(() -> {
if(!future.isDone()) future.complete(resultService.get(processId));
});
return future;
это завершает будущее после тайм-аута, как уже говорилось, не затрагивая текущие вычисления, но предоставляя вызывающему альтернативный результат, но не будет распространять исключения, выданные на resultService.get(processId)
возвращаемому будущее.