You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

CompletableFuture执行中断问题:未捕获CancellationException异常

问题描述

编写了如下代码处理批量异步任务并设置超时:

CompletableFuture<Void> timeoutFuture = CompletableFuture.allOf(futures.toArray(new CompletableFuture[0]))
                .orTimeout(1, TimeUnit.SECONDS); // 为所有任务组合设置超时

CompletableFuture<List<MysticPlanetsNatalChartAspectResponse>> resultFuture = timeoutFuture
        .thenApply(v -> futures.stream()
                .map(CompletableFuture::join)
                .collect(Collectors.toList()));

// 取消未完成的剩余任务
futures.forEach(future -> {
    if (!future.isDone() && !future.isCancelled()) {
        try {
            future.cancel(true);
        } catch (Throwable c) {
            //System.out.println(c.getMessage());
        }
    }
});

运行时异常未被捕获,控制台输出错误:

2023-09-18T21:38:43,869+02:00 [http-nio-8080-exec-1] ERROR o.a.c.c.C.[.[.[.[dispatcherServlet] - Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed: java.util.concurrent.CompletionException: java.util.concurrent.CancellationException] with root cause
java.util.concurrent.CancellationException: null

请问如何解决该异常未被捕获的问题?

解决方法

核心问题是:orTimeout触发超时后会取消timeoutFuture,后续thenApply中调用CompletableFuture::join时,被取消的Future会抛出CancellationException,且当前代码未对resultFuture的异常做处理。以下是具体解决方案:

1. 用handle替代thenApply处理异常

handle支持同时处理正常结果和异常,避免异常向上传播:

CompletableFuture<List<MysticPlanetsNatalChartAspectResponse>> resultFuture = timeoutFuture
        .handle((v, throwable) -> {
            if (throwable != null) {
                // 处理超时或取消异常,返回兜底结果(比如空列表)
                return Collections.emptyList();
            }
            // 过滤被取消的任务,仅处理已正常完成的
            return futures.stream()
                    .filter(future -> !future.isCancelled())
                    .map(CompletableFuture::join)
                    .collect(Collectors.toList());
        });

2. 为resultFuture添加异常回调

通过exceptionally捕获并处理异常:

CompletableFuture<List<MysticPlanetsNatalChartAspectResponse>> resultFuture = timeoutFuture
        .thenApply(v -> futures.stream()
                .filter(future -> !future.isCancelled())
                .map(CompletableFuture::join)
                .collect(Collectors.toList()))
        .exceptionally(throwable -> {
            // 捕获超时导致的取消异常
            if (throwable instanceof CompletionException && throwable.getCause() instanceof CancellationException) {
                return Collections.emptyList();
            }
            // 其他异常可选择重新抛出或自定义处理
            throw new RuntimeException("异步任务处理失败", throwable);
        });

3. 同步获取结果时主动捕获异常

如果需要同步获取resultFuture的结果,在调用get()时用try-catch包裹:

try {
    List<MysticPlanetsNatalChartAspectResponse> result = resultFuture.get();
    // 业务逻辑处理
} catch (InterruptedException | ExecutionException e) {
    if (e.getCause() instanceof CancellationException) {
        // 处理超时取消场景
    } else {
        // 处理其他异常
    }
}

额外优化

当前代码中取消剩余任务的逻辑是在定义resultFuture后立即执行,可能导致正常执行的任务被提前取消。建议将取消逻辑移到异常回调中,仅在超时或异常时取消:

timeoutFuture.exceptionally(throwable -> {
    futures.forEach(future -> {
        if (!future.isDone() && !future.isCancelled()) {
            future.cancel(true);
        }
    });
    return null;
});

内容的提问来源于stack exchange,提问作者Nunyet Calçada

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.10 15:54:51