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

Spring Boot中为何使用runBlocking(Dispatchers.IO)而非runBlocking?

问题

我有一个Spring Boot应用,处理请求时需要并行调用上游服务,等待所有结果返回后再响应。现有代码使用runBlocking(Dispatchers.IO) { ... }实现该逻辑,运行正常。但当将MyUpstreamService的作用域设置为@RequestScope后,在runBlocking(Dispatchers.IO)块中访问该服务会抛出异常:

Caused by: java.lang.IllegalStateException: No thread-bound request found: Are you referring to request attributes outside of an actual web request, or processing a request outside of the originally receiving thread? If you are actually operating within a web request and still receive this message, your code is probably running outside of DispatcherServlet: In this case, use RequestContextListener or RequestContextFilter to expose the current request.
    at org.springframework.web.context.request.RequestContextHolder.currentRequestAttributes(RequestContextHolder.java:131) ~[spring-web-5.3.22.jar:5.3.22]

而不指定Dispatchers.IO的runBlocking { ... }则能正常运行。请问在等待多个异步调用完成时,为何要使用runBlocking(Dispatchers.IO)而非普通的runBlocking?

完整示例代码:

  • GET /bar可正常运行
  • GET /foo抛出异常
@RequestScope
@Service
class MyUpstreamService(
    // val currentUser: CurrentUser
) {
    fun getSomething() = 1
}

@RestController
class MyController(
    val upstream: MyUpstreamService
) {

    @GetMapping("/foo")
    fun foo() =
        runBlocking(Dispatchers.IO) {
            val a = async { upstream.getSomething() }
            val b = async { upstream.getSomething() }
            a.await() + b.await()
        }

    @GetMapping("/bar")
    fun bar() =
        runBlocking {
            val a = async { upstream.getSomething() }
            val b = async { upstream.getSomething() }
            a.await() + b.await()
        }
}

回答

1. 使用runBlocking(Dispatchers.IO)的核心原因

runBlocking默认会复用当前调用它的线程(也就是Spring MVC处理请求的DispatcherServlet线程)来执行协程逻辑。如果在这个线程里执行上游服务调用这类IO密集型任务,会长期占用该线程,导致它无法处理其他请求,直接拉低应用的并发处理能力。

而Dispatchers.IO是Kotlin协程专门为IO密集型任务设计的调度器,它背后是一组共享的后台线程池。用runBlocking(Dispatchers.IO)时,协程会在IO线程池的线程上执行,这样就能立刻释放原来的请求处理线程,让它去承接其他请求,从而提升应用的吞吐量。

2. 指定Dispatchers.IO触发异常的原因

Spring的@RequestScopeBean依赖线程绑定的请求上下文——这个上下文通过RequestContextHolder存储在当前线程中。

当你用runBlocking(Dispatchers.IO)时,async启动的子协程会运行在IO线程池的陌生线程上,这些线程没有绑定当前请求的上下文,所以Spring在创建或访问MyUpstreamService时,找不到请求上下文,就会抛出你看到的异常。

而不加调度器的runBlocking会默认使用调用线程(请求处理线程)执行所有协程逻辑,包括async的子协程也会复用这个线程,请求上下文一直存在,因此能正常访问@RequestScope的Bean。

3. 兼顾性能与请求上下文的解决办法

如果既要用Dispatchers.IO提升并发,又要访问@RequestScopeBean,可以手动把请求上下文传递到IO线程:

@GetMapping("/foo")
fun foo() = runBlocking(Dispatchers.IO) {
    val requestAttributes = RequestContextHolder.getRequestAttributes()
    val a = async {
        RequestContextHolder.setRequestAttributes(requestAttributes)
        try {
            upstream.getSomething()
        } finally {
            RequestContextHolder.resetRequestAttributes()
        }
    }
    val b = async {
        RequestContextHolder.setRequestAttributes(requestAttributes)
        try {
            upstream.getSomething()
        } finally {
            RequestContextHolder.resetRequestAttributes()
        }
    }
    a.await() + b.await()
}

也可以使用Spring提供的ContextCallable工具类,它会自动帮你完成请求上下文的传递与清理,简化代码。

内容的提问来源于stack exchange,提问作者Jan

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.10 16:20:37