基于Reactor+R2DBC+Thymeleaf实现fire and forget异步长任务不阻塞控制器
问题根因分析
你最初代码的核心问题出在getEmployee()方法的实现逻辑上:
- 方法内部的
nap()是同步阻塞调用,会在Mono响应链组装阶段就直接在当前请求线程执行,完全还没到异步订阅执行的环节,所以必然会阻塞接口返回 - 后续调整的写法思路是正确的,需要两次
subscribe()的原因也很明确:repo.save()返回的Mono是冷发布者,只有被订阅才会执行;外层的主响应链本身也是冷的,只有外层调用subscribe()`才会触发整个链路执行
最优实现方案(完全保留注解式Controller写法)
你可以进一步简化代码,避免嵌套调用subscribe(),同时保证所有操作都符合WebFlux非阻塞规范:
@GetMapping(value = "/add") public String add() { // 直接构造员工实例,无阻塞操作直接执行 Employee employee = new Employee(); employee.setName("John"); // 组装后台异步执行链 Mono.just(employee) // 非阻塞延迟,替代原来的Thread.sleep()阻塞调用 .delayElement(Duration.ofSeconds(5)) // 直接拼接R2DBC的保存操作,无需嵌套订阅 .flatMap(repo::save) // 可选:添加异常回调,避免后台任务出错无感知 .doOnError(err -> log.error("员工数据保存失败", err)) // 订阅触发整个异步链执行,不会阻塞当前线程 .subscribe(); // 直接返回视图名,页面会立即渲染 return "add"; }
注意事项
- 不要在WebFlux执行流程中调用
Thread.sleep()这类同步阻塞方法,会占用有限的事件循环线程,导致服务整体吞吐量下降,所有延迟/耗时操作都要使用Reactor提供的非阻塞算子实现 - 后台异步任务建议自定义专用
Scheduler调度,避免共用Reactor默认公共线程池,影响主业务流程性能,调度写法参考如下:
// 建议将调度器作为Bean注入容器复用 private final Scheduler asyncTaskScheduler = Schedulers.newBoundedElastic(10, 100, "emp-task"); // 异步链指定专属调度器执行 Mono.just(employee) .delayElement(Duration.ofSeconds(5)) .flatMap(repo::save) .doOnError(err -> log.error("员工数据保存失败", err)) .subscribeOn(asyncTaskScheduler) .subscribe();
- fire and forget类型的异步任务一定要添加异常处理逻辑,否则异步任务的异常默认不会抛出到主线程,出错后没有任何感知
- 你原有
home接口的写法符合规范,Thymeleaf原生支持将Reactive类型作为Model属性,会自动等待流结果完成后渲染页面。
内容的提问来源于stack exchange,提问作者53c
相关产品推荐
相关产品推荐

