java 向CompletableFuture或Timeout添加说明

t1rydlwq  于 2023-06-28  发布在  Java
关注(0)|答案(4)|浏览(95)

什么是简单的方法来添加一些额外的信息到超时异常?假设我有一个这样的代码:

CompletableFuture<ResutType> getSomething()
{
  ... return someFuture.orTimeout(15, SECONDS);
}

这是我在稍后尝试执行get()的方法的调用者时获得的所有下游信息:

java.util.concurrent.ExecutionException: java.util.concurrent.TimeoutException
    at java.base/java.util.concurrent.CompletableFuture.reportGet(CompletableFuture.java:396)
    at java.base/java.util.concurrent.CompletableFuture.get(CompletableFuture.java:2073)

我需要添加更多的信息-哪个操作超时了,超时了多长时间,等等。换句话说,我需要一些异常消息,比如“15秒内超时的东西”,理想情况下使用这样的供应商:

CompletableFuture<ResutType> getSomething()
{
   return someFuture.orTimeout(15, SECONDS, () -> new TimeoutException("The Something timed-out in 15s"));
}
gwbalxhn

gwbalxhn1#

您可以使用与CompletableFuture的内部超时处理相同的工具:

public static <T> CompletableFuture<T> customTimeOut(
    CompletableFuture<T> cf, long time, TimeUnit unit,
    Supplier<? extends Exception> s) {

    CompletableFuture.delayedExecutor(time, unit, cf.defaultExecutor())
        .execute(() -> { if(!cf.isDone()) cf.completeExceptionally(s.get()); });

  return cf;
}

请注意,isDone()检查不是严格必要的,因为如果future已经完成,completeExceptionally就不会执行任何操作。检查只是为了防止在不必要时进行潜在的昂贵的异常构造。这仍然是可能的,但不太可能在isDone()completeExceptionally调用之间完成未来,但没有什么不好的事情会发生,因为completeExceptionally无论如何都做了正确的事情。
你可以像这样测试它

public static void main(String[] args) {
    for(int timeOut = 1; timeOut < 5; timeOut += 2) {
        System.out.println("time-out " + timeOut + " sec");
  
        CompletableFuture<String> cf = customTimeOut(
            CompletableFuture.supplyAsync(() -> {
                LockSupport.parkNanos(2_000_000_000);
                return "result";
            }),
            timeOut, TimeUnit.SECONDS,
            () -> new TimeoutException("timeout with my custom message")
        );
    
        try {
            System.out.println("ordinary result: " + cf.join());
        }
        catch(Exception ex) {
            ex.printStackTrace(System.out);
        }
    }
}
time-out 1 sec
java.util.concurrent.CompletionException: java.util.concurrent.TimeoutException: timeout with my custom message
    at java.base/java.util.concurrent.CompletableFuture.reportJoin(CompletableFuture.java:413)
    at java.base/java.util.concurrent.CompletableFuture.join(CompletableFuture.java:2118)
    at CF.main(CF.java:32)
Caused by: java.util.concurrent.TimeoutException: timeout with my custom message
    at CF.lambda$2(CF.java:28)
    at CF.lambda$0(CF.java:13)
    at java.base/java.util.concurrent.ForkJoinTask$RunnableExecuteAction.exec(ForkJoinTask.java:1395)
    at java.base/java.util.concurrent.ForkJoinTask.doExec(ForkJoinTask.java:373)
    at java.base/java.util.concurrent.ForkJoinPool$WorkQueue.topLevelExec(ForkJoinPool.java:1182)
    at java.base/java.util.concurrent.ForkJoinPool.scan(ForkJoinPool.java:1655)
    at java.base/java.util.concurrent.ForkJoinPool.runWorker(ForkJoinPool.java:1622)
    at java.base/java.util.concurrent.ForkJoinWorkerThread.run(ForkJoinWorkerThread.java:165)
time-out 3 sec
ordinary result: result

在线试试!

jhdbpxl9

jhdbpxl92#

您可以使用exceptionallyCompose(或此方法的任何异步变体):

public final class Example {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
                    sleep(3000);
                    return "DONE";
                })
                .orTimeout(1000, TimeUnit.MILLISECONDS)
                .exceptionallyCompose(throwable -> {
                    if (throwable instanceof TimeoutException) {
                        var tex = new TimeoutException("The Something timed-out in 15s");
                        return CompletableFuture.failedFuture(tex);
                    }
                    return CompletableFuture.failedFuture(throwable);
                });

        sleep(2000);
        future.get();
        sleep(5000);
    }

    private static void sleep(long ms) {
        try {
            Thread.sleep(ms);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
    }
}

如果你的代码由于TimeoutException而失败,你可以通过一个失败的CompletableFuture返回一个TimeoutException(或任何其他可抛出的),它有更多的细节。否则,您可以返回导致前一阶段异常完成的同一个throwable。
例如,如果supplyAsync中有一些RuntimeExceptionfuture.get()仍然会导致抛出RuntimeException( Package 在ExecutionException中)。如果没有例外(即也没有超时),你get()你的结果正常.
有了这些知识,你可以编写自己的orTimeout封装器,例如:

public static <T> CompletableFuture<T> orTimeout(
        CompletableFuture<T> cf,
        long timeout, TimeUnit unit
) {
    return cf.orTimeout(timeout, unit).exceptionallyCompose(throwable -> {
        if (throwable instanceof TimeoutException) {
            final var msg = String.format(
                    "The Something timed-out in %d %s.",
                    timeout, unit
            );
            return CompletableFuture.failedFuture(new TimeoutException(msg));
        }
        return CompletableFuture.failedFuture(throwable);
    });
}

然后将其用于所有CompletableFuture s,如下所示:

CompletableFuture<String> future = orTimeout(CompletableFuture.supplyAsync(() -> {
    sleep(10000);
    return "DONE";
}), 1000, TimeUnit.MILLISECONDS);
ecbunoof

ecbunoof3#

由于您期望超时,因此考虑将其作为返回类型的一部分,而不是处理TimeoutException。一种方法是使用Vavr libraryEither类型将结果类型更改为Either<Timeout, Result>

var future = CompletableFuture.<Either<Timeout, Result>>supplyAsync(() ->
    Either.right(lengthyCalculation()))
.completeOnTimeout(
    Either.left(new Timeout("Lengthy Calculation timed out", 12)), 1, SECONDS);

一旦future完成并且您获得了它的值,使用Either的许多方法之一继续。在最简单的情况下

Result result = future.get()
    .getOrElseThrow(timeout -> new RuntimeException(...));
j9per5c4

j9per5c44#

天真地说。

CompletableFuture future = CompletableFuture.runAsync( () ->{
    try{
        someFuture.get(15, TimeUnit.MILLISECONDS);
    } catch(Exception e){
        throw new RuntimeException("detailed message", e);
    }
});

当然,你应该更具体地说明例外情况,但我认为这是问题的要点。

相关问题