Как правило, просто повторная операция при возникновении исключения, без учета исключения, чтобы проанализировать причину сбоя, далека от создания надежной системы.
Тем не менее, если вы хотите реализовать повторные попытки, ваш код не сделает это правильно.
Ваш код должен быть принят компилятором, потому что вы используете действие, которое не дает значения и возвращает CompletableFuture<?>
. Это скрывает проблемы с вашим кодом:
Предполагается, что бифурка, переданная в handleAsync
, должна предоставить значение результата, но вы вызываете this.recursion()
, который производит CompletableFuture<?>
. Компилятор не возражает против того, что в неисключительном случае возвращается v
, так как Void
и CompletableFuture<?>
имеют общий тип супер, Object
, поэтому весь метод эффективно возвращает CompletableFuture<Object>
, который совместим с типом возврата CompletableFuture<?>
.
Если объявлен типа возвращаемого значения, как CompletableFuture<Void>
, логическая ошибка стала узнаваема сразу: в исключительном случае, если вы запускаете другую асинхронную операцию, но так как вы не проверить это результат, а просто возвращаете CompletableFuture<?>
, который затем обрабатывают, как Object
, вызывающий абонент никогда не заметит, произошла ли повторная попытка (или последующие попытки). Вызывающий всегда будет получать CompletableFuture<?>
, который сообщает об успешном завершении, либо (Void)null
, либо CompletableFuture<?>
в качестве результата.
Как правило, вы не должны использовать рекурсию для повторения. Для этого нет оснований. Давайте продемонстрировать логику действия, которая возвращает значение:
CompletableFuture<String> performAsyncAction() {
Supplier<String> action=() -> {
if(Math.random()>0.2)
throw new IllegalStateException("simulated failure");
return "value implying success";
};
int retries=5;
return CompletableFuture.supplyAsync(() -> {
try { return action.get(); }
catch(Throwable t) {
for(int i=0; i<retries; i++) try {
Thread.sleep(1000);
return action.get();
} catch(Throwable next) { t.addSuppressed(next); }
throw t;
}
});
}
Это легко адаптируются использовать Runnable
, runAsync
и CompletableFuture<Void>
.
Обновление: если вы просто хотите запланировать повторную попытку, не давая обратную связь инициатора, вы можете реализовать его, не блокируя поток, ожидая задержки истечь:
static ScheduledExecutorService e = Executors.newSingleThreadScheduledExecutor();
static void performAsyncAction(Runnable r, int tries, long delay, TimeUnit u) {
if(tries>0)
e.execute(()-> { try { r.run(); } catch(Throwable t) {
e.schedule(()->performAsyncAction(r, tries-1, delay, u), delay, u);
}});
}
При этом используется рекурсия, когда она оседает на лямбда-выражениях.То же самое будет работать без рекурсии, если вы используете внутренний класс:
static ScheduledExecutorService e = Executors.newSingleThreadScheduledExecutor();
static void performAsyncAction(Runnable r, int tries, long delay, TimeUnit u) {
if(tries>0)
e.execute(new Runnable() {
int left = tries;
public void run() {
try { r.run(); } catch(Throwable t) {
if(--left > 0) e.schedule(this, delay, u);
}
}
});
}
Вы должны также принимать в том, что с помощью рекурсии может взорвать с 'StackOverflowError' или бесконечным циклом. – Tunaki
Да, я считаю, что на самом деле считается, что количество попыток и спать между каждой попыткой. –
Такие вещи уже существуют, но я предлагаю вам взглянуть на 'rxjava'. Существует также «asyn-cretry», который хорош: https://github.com/nurkiewicz/async-retry –