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
相关产品推荐
相关产品推荐

