Spring Boot异步控制器仍阻塞:使用CompletableFuture与@Async后仍等待
Spring Boot异步控制器请求超时问题分析与解决
问题场景
开发Spring Boot应用时,尝试通过CompletableFuture和@Async注解实现控制器异步处理。测试时在服务方法中加入Thread.sleep()模拟耗时操作,但控制器仍会等待并最终抛出AsyncRequestTimeoutException,已确认控制器与服务运行在不同线程,需排查原因并解决。
相关代码
控制器代码
@PostMapping public CompletableFuture<User> createUser(@RequestBody User user) { System.out.println(Thread.currentThread().getId()+"-----------------------------------"+System.currentTimeMillis()+"----------"+"inside controller"); return userService.createUser(user); }
服务层代码
@Override @Async("asyncExecutor") public CompletableFuture<User> createUser(User user) { System.out.println(Thread.currentThread().getId()+"-----------------------------------"+System.currentTimeMillis()+"-------------inside services"); return CompletableFuture.supplyAsync(() ->{ try { Thread.sleep(1000000000); } catch (InterruptedException e) { throw new RuntimeException(e); } System.out.println(Thread.currentThread().getId()); return userRepository.save(user); }); }
控制台输出
21-----------------------------------1683404458540----------inside controller 34-----------------------------------1683404458543-------------inside services 2023-05-07 01:51:28.812 WARN 36119 --- [nio-8080-exec-2] .w.s.m.s.DefaultHandlerExceptionResolver : Resolved [org.springframework.web.context.request.async.AsyncRequestTimeoutException]
问题原因分析
- 双重异步嵌套+超长时间任务:服务层方法已通过
@Async标记为异步执行,内部又调用CompletableFuture.supplyAsync()做了二次异步嵌套。Spring MVC的异步请求会等待返回的CompletableFuture完成,但你设置的Thread.sleep(1000000000)(约277小时)远超Spring默认的异步请求超时时间(通常30秒),必然触发超时异常。 - 默认超时阈值限制:Spring Boot异步请求默认超时时间为30秒,只要异步任务执行时间超过这个值,就会抛出
AsyncRequestTimeoutException。
解决建议
1. 移除不必要的异步嵌套
服务层的@Async已经能实现方法异步执行,无需再嵌套CompletableFuture.supplyAsync(),直接同步执行逻辑后返回已完成的CompletableFuture即可:
@Override @Async("asyncExecutor") public CompletableFuture<User> createUser(User user) { System.out.println(Thread.currentThread().getId()+"-----------------------------------"+System.currentTimeMillis()+"-------------inside services"); try { Thread.sleep(1000000000); } catch (InterruptedException e) { throw new RuntimeException(e); } System.out.println(Thread.currentThread().getId()); return CompletableFuture.completedFuture(userRepository.save(user)); }
2. 调整异步请求超时时间
如果确实需要长时间运行的异步任务,可在配置文件中修改超时阈值:
# 设置超时时间为3600秒(1小时) spring.mvc.async.request-timeout=3600000
或通过Java配置类自定义:
@Configuration public class WebMvcConfig implements WebMvcConfigurer { @Override public void configureAsyncSupport(AsyncSupportConfigurer configurer) { // 设置超时时间为3600秒 configurer.setDefaultTimeout(3600000); } }
3. 替换超长时间阻塞的测试场景
Thread.sleep(1000000000)是极端测试用例,实际业务中应避免此类长时间同步阻塞。若需处理耗时任务,建议使用消息队列(如RabbitMQ、Kafka)实现任务异步解耦:控制器直接返回任务提交成功的响应,后续通过回调、推送等方式通知用户结果。
内容的提问来源于stack exchange,提问作者Deepak Mathur
相关产品推荐
相关产品推荐

