【问题标题】:Why would one use runBlocking(IO) instead of just runBlocking in a Spring Boot app为什么要在 Spring Boot 应用程序中使用 runBlocking(IO) 而不是仅仅使用 runBlocking
【发布时间】:2022-12-17 00:32:39
【问题描述】:

我有一个 Spring Boot 应用程序,在处理给定请求时,我需要并行调用上游服务并等待结果完成,然后再在我自己的响应中返回它们。

在现有的代码库中,我注意到为了这样做,模式是使用runBlocking(IO) { ... }

@Service
class MyUpstreamService {
    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()
        }
}

这按预期工作。

现在由于某些原因,我需要将 MyUpstreamService 的范围设置为 @RequestScope,如果我这样做,一旦我从 runBlocking(IO) { ... } 块中访问 MyUpstreamService,就会出现以下异常:

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(Dispatchers.IO) { .. } 而不是仅使用 runBlocking { .. }

为了完整起见,这里是演示问题的整个 sn-p。

  • 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()
        }
}

【问题讨论】:

    标签: spring kotlin kotlin-coroutines


    【解决方案1】:

    runBlocking 没有特定的调度程序意味着内部的所有协程都在一个特殊的单线程事件循环调度程序中启动,该调度程序由您阻止的线程支持。反过来,这意味着您的协程不会并行运行。

    runBlocking(Dispatchers.IO) 表示嵌套协程在 IO 调度程序上运行,它由动态大小的线程池支持,因此协程有效地并行运行(在一定限度内)。同时,它仍然是一个runBlocking,这意味着调用线程在等待嵌套协程完成时仍然会被阻塞,但不会被用来做任何工作。

    由于某些原因,我需要将 MyUpstreamService 的范围设置为 @RequestScope

    当您这样做时,Spring 会根据请求创建一个服务实例——这是根据请求的线(我假设使用一些ThreadLocal 机器)。正如我们刚刚看到的,runBlocking 没有调度程序实际上使用调用线程,所以请求线程,这就是为什么这个机制仍然有效的原因。如果您使用 runBlocking(IO) 并在其他线程上分派,那么您将破坏此 Spring 机制。

    现在我有一段时间没有做过 Spring 开发,所以我不是 100% 确定如何解决你的问题。但我相信,如果您正在使用协程,那么停止使用每个请求线程模型是一个好的开始,因此切换到suspend functions in your controllers using Spring WebFlux。不过,我认为它仍然不允许使用@RequestScope,因为您将完全放弃“请求线程”的概念。见https://github.com/spring-projects/spring-framework/issues/28235

    【讨论】:

    • 一种解决方案是创建一个 ThreadContextElement 来更新 Spring 的 RequestContextHolder
    猜你喜欢
    • 1970-01-01
    • 2021-11-14
    • 2020-08-06
    • 2014-06-26
    • 1970-01-01
    • 2021-08-17
    • 1970-01-01
    • 1970-01-01
    • 2012-08-26
    相关资源
    最近更新 更多