【问题标题】:CompletableFuture recursive restart on exception from exceptionaly() blockCompletableFuture 在 exceptiony() 块异常时递归重启
【发布时间】:2021-07-29 04:53:37
【问题描述】:

很难对后台任务进行可靠的重试,该任务会向邮件服务发送请求以获取最新的电子邮件。成功接收电子邮件后,应在thenAccept() 块中继续执行 - 保留电子邮件,但是如果发生异常,我必须重新运行邮件检索,直到成功尝试,成功后应该保留邮件并停止。如果我做错了,请看看并提出建议。

   private void retrieveMailsAsync(User user) {
    CompletableFuture.supplyAsync(() -> {
        try {
            return mailService.getEmails(user.getName(), user.getPassword());
        } catch (InvalidAuthentication | TimeoutException | BadGatewayException e) {
            throw new CompletionException(e);
        }
    }).thenAccept(email -> {
        mailService.persist(email);
    }).exceptionally(ex -> {
        log.log(Level.SEVERE, "Exception retrieveMailsAsync emails, Retrying retrieveMailsAsync:: ", ex.getCause());
        retrieveMailsAsync(user);
        return null;
    });
}

PS 也请看看我是如何处理检查异常,将其包装到 CompletionException 并重新抛出 - 这里的主要思想是在一个 exceptionally() 块中处理所有异常(定义检查和运行时)而不是记录它们在 catch 块中并返回 null。

提前感谢大家,希望我没有做一些愚蠢的事情,或者至少已经有适用于 Java 8 的可靠解决方案。

【问题讨论】:

  • 这是独立代码还是在spring等一些框架下,有了spring有更好的重试方式吗?也让我们知道是什么问题,您是否异常不重试活动?
  • 如果您至少有 jdk-12,这在某种程度上很容易,对吗?
  • 不幸的是,它是没有任何框架的普通 Java 8((这里的问题是,以这种方式处理重试是否是有效的案例 - 从异常块递归?
  • 也许我理解错了,但是如果你想重试直到成功,为什么你首先抛出完成异常?您可以在运行部分循环,直到您毫无例外地离开。
  • @daniu,很抱歉造成混淆。问题是 mailService 抛出了我必须处理的检查异常,所以我将它们包装在运行时 CompletionException 中,这样我试图在一个地方收集异常处理登录 - 例如在 exceptionly() 块中。否则,我必须在 catch{} 块中处理检查的异常和重试逻辑一次,在异常块中处理其他未经检查的 + 重试逻辑。但是我的解决方案在一个地方积累了这个逻辑。希望如此)

标签: java recursion exception completable-future


【解决方案1】:

我在评论中的意思是:

   private void retrieveMailsAsync(User user) {
    CompletableFuture.supplyAsync(() -> {
        while (continueQuery()) { // true for infinite retries, or some other logic
          try {
              return mailService.getEmails(user.getName(), user.getPassword());
          } catch (InvalidAuthentication | TimeoutException | BadGatewayException e) {
              log.log(Level.SEVERE, "Exception retrieveMailsAsync emails, Retrying retrieveMailsAsync: ", e);
          }
        }
        return null;
    }).thenAccept(email -> {
        mailService.persist(email);
    });
}

也就是说,您只需在 submitted 可运行项中重试,直到不再出现异常。

【讨论】:

    【解决方案2】:

    我认为您可以通过以下方式实现:

    public static void main(String[] args) {
        String result = call(new User().setName("name").setPassword("p")).join();
        System.out.println(result);
    }
    
    private static CompletableFuture<String> call(User user) {
        CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> retrieveMailsAsync(user));
        return future.handleAsync((String result, Throwable ex) -> {
            // or any other Predicate that is satisfied against ex
            if(ex != null) {
                return call(user);
            } else {
                return future;
            }
        }).thenCompose(Function.identity());
    }
    

    编辑

    那么,还有什么阻碍你改变上面的代码,例如:

    static ExecutorService service = Executors.newFixedThreadPool(1);
    
    public static void main(String[] args) {
        call(new User().setName("name").setPassword("p"))
             // chain any other action here, like mailService.persist(email);
             .thenAcceptAsync(
                System.out::println,
                service
        );
        System.out.println("Continue main thread");
    }
    
    private static CompletableFuture<String> call(User user) {
        CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> retrieveMailsAsync(user), service);
        return future.handleAsync((String result, Throwable ex) -> {
            // or any other Predicate that is satisfied against ex
            if(ex != null) {
                return call(user);
            } else {
                return future;
            }
        }).thenCompose(Function.identity());
    }
    

    【讨论】:

    • @Eugeen,感谢您的回复。但是使用 .join() 我们不会阻塞主线程以等待异步块的完成吗?我真正需要的是启动 Future 任务以在后台检索和保存用户电子邮件,而主线程应该继续进行而没有任何延迟。并让这些事情变得可靠,所以如果后台线程中有任何异常 - 任务应该重试自身直到成功。
    • @AnMi 所以,你需要像异步任务一样的任务吗?
    • @Eughen,是的,我需要这个完整的retrieveMailsAsync() 方法是异步的并在后台运行,它不会停止主线程(只需启动并忘记),而且它必须可靠地重新启动自身以防异常阻塞。
    • @AnMi 查看编辑。但如果你只是想运行异步任务,我认为你不需要CompletableFuture
    猜你喜欢
    • 1970-01-01
    • 2017-01-13
    • 2019-03-30
    • 1970-01-01
    • 2015-12-03
    • 1970-01-01
    • 2018-01-03
    • 2017-11-08
    • 2011-05-05
    相关资源
    最近更新 更多