gpt4 book ai didi

spring - 如何避免在执行长时间运行计算的 Spring WebFlux Controller 中使用 Kotlin Coroutines 的 GlobalScope

转载 作者:行者123 更新时间:2023-12-02 01:55:57 27 4
gpt4 key购买 nike

我有一个 Rest API,它是使用 Spring WebFlux 和 Kotlin 实现的,带有一个端点,用于启动长时间运行的计算。由于让调用者等到计算完成并不是很优雅,它应该立即返回一个 ID,调用者可以使用该 ID 在不同的端点上获取可用的结果。计算在后台启动,应该在准备就绪时完成 - 我并不真正关心它何时完成,因为轮询它是调用者的工作。

当我使用 Kotlin 时,我认为解决这个问题的规范方法是使用协程。这是我的实现方式的一个最小示例(使用 Spring's Kotlin DSL 而不是传统 Controller ):

import org.springframework.web.reactive.function.server.coRouter

// ...

fun route() = coRouter {
POST("/big-computation") { request: ServerRequest ->
val params = request.awaitBody<LongRunningComputationParams>()
val runId = GlobalResultStorage.prepareRun(params);
coroutineScope {
launch(Dispatchers.Default) {
GlobalResultStorage.addResult(runId, longRunningComputation(params))
}
}
ok().bodyValueAndAwait(runId)
}
}

但是,这并没有按照我的意愿进行,因为外部协程(POST("/big-computation") 之后的 block )会等待其内部协程完成执行,因此仅在不再需要它之后返回 runId

我能找到的唯一可能的方法是使用 GlobalScope.launch,它会生成一个没有等待其结果的父进程的协程,但我到处都读到你强烈反对使用它。需要明确的是,有效的代码如下所示:

POST("/big-computation") { request: ServerRequest ->
val params = request.awaitBody<LongRunningComputationParams>()
val runId = GlobalResultStorage.prepareRun(params);
GlobalScope.launch {
GlobalResultStorage.addResult(runId, longRunningComputation(params))
}
ok().bodyValueAndAwait(runId)
}

我是否遗漏了一些非常明显的东西,这些东西会使我的示例使用适当的结构化并发工作,或者这真的是 GlobalScope 的合法用例吗?有没有办法在一个不附加到它启动的范围的范围内启动长期运行计算的协程?我能想到的唯一想法是从同一个 coroutineScope 启动计算和请求处理程序,但是因为计算取决于请求处理程序,所以我不明白这怎么可能。

提前致谢!

最佳答案

也许其他人不会同意我的看法,但我认为这种对 GlobalScope 的厌恶有点夸张了。我经常有这样的印象,有些人并不真正理解 GlobalScope 的问题所在,他们用具有相似缺点或实际上相同的解决方案代替它。但是,至少他们不再使用邪恶的 GlobalScope 了......

不要误会我的意思:GlobalScope 不好。特别是因为它太容易使用了,所以很容易过度使用它。但在很多情况下,我们并不真正关心它的缺点。

结构化并发的主要目标是:

  • 自动等待子任务,这样我们就不会意外地在子任务完成之前继续执行。
  • 取消个别工作。
  • 取消/关闭安排后台任务的服务/组件。
  • 在异步任务之间传播失败。

这些特性对于提供可靠的并发应用程序至关重要,但令人惊讶的是,在很多情况下它们都不重要。让我们举个例子:如果您的请求处理程序在应用程序的整个时间内都在工作,那么您不需要既等待子任务又关闭功能。您不想传播失败。取消单个子任务在这里并不适用,因为无论我们使用 GlobalScope 还是“适当的”解决方案,我们都完全相同 - 通过将任务的 Job 存储在某处。

因此,我想说 GlobalScope 不被鼓励的主要原因并不适用于您的情况。

话虽如此,我仍然认为实现通常建议作为 GlobalScope 的适当替代方案的解决方案可能是值得的。只需使用您自己的 CoroutineScope 创建一个属性并使用它来启动协程:

private val scope = CoroutineScope(Dispatchers.Default)

fun route() = coRouter {
POST("/big-computation") { request: ServerRequest ->
...
scope.launch {
GlobalResultStorage.addResult(runId, longRunningComputation(params))
}
...
}
}

你不会从中得到太多。它不会帮助您解决资源泄漏问题,也不会使您的代码更可靠或其他什么。但至少它将有助于以某种方式对后台任务进行分类。技术上可以确定谁是后台任务的所有者。您可以轻松地在一个地方配置所有后台任务,例如提供 CoroutineName 或切换到另一个线程池。您可以计算当前有多少事件子任务。如果需要,添加优雅关机会更容易。等等。

但最重要的是:实现起来很便宜。您不会得到太多,但也不会花费太多,所以为什么不呢。

关于spring - 如何避免在执行长时间运行计算的 Spring WebFlux Controller 中使用 Kotlin Coroutines 的 GlobalScope,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/69600247/

27 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com