【问题标题】:What is the CoroutineScope of runBlocking in Kotlin Coroutines?Kotlin Coroutines 中 runBlocking 的 CoroutineScope 是什么?
【发布时间】:2021-09-20 16:28:25
【问题描述】:

什么是CoroutineScope

runBlocking {
 // some code here
}

在 Kotlin 协程中?

它是在调用它的类本地并且当类被垃圾收集时协程被垃圾收集还是如果它仍在运行会导致内存泄漏?

【问题讨论】:

  • runBlocking 是普通代码和协程之间的桥梁。它阻塞了调用它的线程。协程不是线程,协程可以在一个线程上开始工作并在另一个线程上完成。使用this 的协程作用域就是那个线程协程作用域,你可以在代码块中使用任何其他的作用域。为了回答您的问题,如果 runBlocking 的作用域被破坏,则在 runBlocking 内运行的协程应该停止,但这不应该在现实世界的示例中使用,因为通常 runBlocking 应该只在顶层,就像在 main 函数中一样。

标签: kotlin kotlin-coroutines


【解决方案1】:

launchasync 不同,runBlocking 是一个非常特殊的协程构建器,因为它应该在顶层使用。因此它不会在 inside 范围内运行(如您所见,没有 CoroutineScope 作为接收者)。它应该是结构化并发的“根”。

runBlocking 实际上为您提供了一个范围,因此您可以在其中启动“子”协程。真正重要的是runBlocking 将等待您在该范围内启动的所有协程(子协程)完成后再返回。

取消 runBlocking?

如果 runBlocking 协程挂起,您不能真正从外部取消它本身(就像您通过取消 launchasync 的范围所做的那样),因为它不是异步任务 - 它正在阻塞线程。您可以中断运行它的线程,或者您也可以跟踪嵌套协程并显式取消 它们 以使 runBlocking 完成。

只有在所有子协程完成(或被取消)后,runBlocking 调用才会返回。抛出异常(从内部)也会取消所有子协程并使runBlocking 重新抛出该异常。所以这也是一种从内部“取消”runBlocking 的方式。

它是调用它的类的本地,并且当类被垃圾收集时协程被垃圾收集,还是如果它仍在运行会导致内存泄漏?

您可以将runBlocking 视为任何阻塞函数,例如Thread.sleep,没有更多的魔法。就像Thread.sleep 一样,runBlocking 可以从任何函数调用,甚至是顶级函数,在这种情况下不会涉及任何类实例。

让我们假设一个类中的方法调用runBlocking,而runBlocking 中的任何东西都会挂起很长时间(就像长时间的睡眠一样)。然后,调用此方法的任何人都持有对该实例的引用,直到该方法返回或失败,因此该实例无论如何都不会被垃圾回收。在这种情况下,调用者将挂起,阻塞它正在运行的任何线程——这就是可能发生泄漏的地方。

runBlocking 问题示例

弹簧控制器

如果您在 Spring MVC 控制器的方法中使用 runblocking,则 Spring 可能会为其中的每一个创建线程,如果有任何东西挂在 runBlocking 中,您可能会泄漏线程。相反,您可以使用 Spring WebFlux,它允许您直接在 Spring 控制器中使用 suspend 函数(并添加请求超时以自动取消挂起的东西)。

回调

在回调中使用runBlocking 也可能很危险。您正在使用的假设基于回调的 API 可能正在使用线程池来回调您,如果 runBlocking 挂起,它可能会阻塞该线程池。

如果该 API 支持背压,则可以阻止。 API 还可以以非阻塞方式支持这一点(例如 JDK11 的 websocket listener,它允许您从回调中返回 CompletableStage),在这种情况下,您应该构建 Future 而不是阻塞。

如果 API 不支持背压,您可能不得不求助于在其中创建自定义 CoroutineScopelaunch-ing 协程来处理回调(而不是 runBlocking)。当您不再使用基于回调的 API 时,您必须手动取消该范围。

【讨论】:

  • 我相信 OP 的主要观点是我们有某种服务,它使用runBlocking() 运行协程,然后我们删除对该服务的所有引用。问题是:它会泄露后台任务吗?像这样设计代码是否安全?我相信答案是:不,这不安全。我们不能通过某种方式删除对协程的所有引用来停止协程。我们总是需要明确地取消它们。此外,该服务不会真正进行 GC,因为在 runBlocking() 上阻塞的线程将保留对它的引用。
  • 但是调用runBlocking 直到它完成才会返回,所以它永远不会泄漏任何东西。因为我所说的泄漏是在不清理资源的情况下返回控制流。唯一可能泄漏的是正在运行的runBlocking,例如,如果它正在生成线程来运行它,但这是另一个话题。 我们总是需要显式取消它们。 - 实际上,不,等待runBlocking 返回足以保证所有协程都在没有显式取消的情况下完成。
  • 编辑了我的问题。这是一个新问题:它是调用它的类的本地,并且当类被垃圾收集时协程被垃圾收集,还是如果它仍在运行会导致内存泄漏? @乔弗里
  • @Gissipi_453 我试图编辑我的答案以解决您的问题,并澄清我上面评论的意思。如果还有什么不清楚的地方请告诉我。
猜你喜欢
  • 2020-07-12
  • 2017-12-01
  • 1970-01-01
  • 2023-03-07
  • 1970-01-01
  • 2020-11-21
  • 2020-04-09
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多