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

基于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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.24 20:54:04