so-*_*ude 7 jvm-languages kotlin kotlinx.coroutines
我在这里Globalscope强烈建议不要使用。
我有一个简单的用例。对于我收到的每条kafka消息(比如说一个ID列表),我必须将其拆分并同时调用rest服务,等待它完成并继续执行其他同步任务。该应用程序中没有其他需要协程的东西。在这种情况下,我可以摆脱它吗?
注意:这不是android应用程序。它只是在服务器端运行的kafka流处理器。这是一个在Kubernetes中运行的临时性,无状态,容器化(Docker)应用程序(如果愿意的话,它支持Buzzword)
mar*_*ran 22
您应该使用结构化并发适当地确定并发范围。如果您不这样做,您的协程可能会泄漏。在您的情况下,将它们范围限定为处理单个消息似乎是合适的。
下面是一个例子:
/* I don't know Kafka, but let's pretend this function gets
* called when you receive a new message
*/
suspend fun onMessage(msg: Message) {
val ids: List<Int> = msg.getIds()
val jobs = ids.map { id ->
GlobalScope.launch { restService.post(id) }
}
jobs.joinAll()
}
Run Code Online (Sandbox Code Playgroud)
如果其中一个调用restService.post(id)失败并出现异常,该示例将立即重新抛出异常,所有尚未完成的作业都将泄漏。它们将继续执行(可能无限期地执行),如果它们失败了,您将一无所知。
要解决此问题,您需要确定协程的范围。这是没有泄漏的相同示例:
suspend fun onMessage(msg: Message) = coroutineScope {
val ids: List<Int> = msg.getIds()
ids.forEach { id ->
// launch is called on "this", which is the coroutineScope.
launch { restService.post(id) }
}
}
Run Code Online (Sandbox Code Playgroud)
在这种情况下,如果其中一个调用restService.post(id)失败,则协程范围内的所有其他未完成的协程将被取消。当你离开作用域时,你可以确定你没有泄露任何协程。
此外,因为coroutineScope会等到所有子协程完成,所以您可以放弃jobs.joinAll()调用。
旁注:编写启动一些协程的函数时的约定是让调用者使用接收器参数决定协程范围。使用该onMessage函数执行此操作可能如下所示:
fun CoroutineScope.onMessage(msg: Message): List<Job> {
val ids: List<Int> = msg.getIds()
return ids.map { id ->
// launch is called on "this", which is the coroutineScope.
launch { restService.post(id) }
}
}
Run Code Online (Sandbox Code Playgroud)
Ser*_*gey 10
非常不鼓励在实例上使用 async 或 launchGlobalScopeCoroutineScope的文档,应用程序代码通常应该使用 application-defined 。
如果我们查看 的定义,GlobalScope我们将看到它被声明为object:
object GlobalScope : CoroutineScope { ... }
Run Code Online (Sandbox Code Playgroud)
一个对象代表一个静态实例(Singleton)。在Kotlin/JVM 中,当一个类被 JVM 加载时,一个静态变量就会存在,当类被卸载时就会消失。当您第一次使用GlobalScope它时,它将被加载到内存中并保持在那里,直到发生以下情况之一:
因此,它会在您的服务器应用程序运行时消耗一些内存。即使您的服务器应用程序已完成运行但进程没有被销毁,启动的协程可能仍在运行并消耗内存。
使用GlobalScope.asyncorGlobalScope.launch将创建一个顶级“独立”协程从全局范围启动一个新的协程。
提供协程结构的机制称为结构化并发。让我们看看结构化并发对全局范围有什么好处:
- 作用域一般负责子协程,它们的生命周期依附于作用域的生命周期。
- 如果出现问题,或者如果用户只是改变主意并决定撤销操作,范围可以自动取消子协程。
- 作用域会自动等待所有子协程完成。因此,如果作用域对应一个协程,那么父协程直到其作用域内启动的所有协程都完成后才会完成。
使用GlobalScope.async时没有将多个协程绑定到较小范围的结构。从全局范围启动的协程都是独立的;它们的生命周期仅受整个应用程序的生命周期限制。可以存储对从全局范围启动的协程的引用并等待其完成或显式取消它,但它不会像结构化的那样自动发生。如果我们想取消范围内的所有协程,使用结构化并发,我们只需要取消父协程,这会自动将取消传播到所有子协程。
如果您不需要将协程范围限定为特定的生命周期对象,并且您想要启动一个在整个应用程序生命周期内运行且不会过早取消的顶级独立协程,并且您不想使用在结构化的并发,然后继续使用全球范围。
在您的链接中指出:
应用程序代码通常应该使用应用程序定义的
CoroutineScope, 强烈建议不要在 的实例上使用async或。launchGlobalScope
我的回答就是针对这个问题的。
一般来说GlobalScope可能是个坏主意,因为它不与任何工作绑定。您应该将它用于以下用途:
全局范围用于启动顶级协程,这些协程在整个应用程序生命周期内运行并且不会提前取消。
这似乎不是你的用例。
有关更多信息,请参阅官方文档中的一段话:https://kotlinlang.org/docs/reference/coroutines/basics.html#structured-concurrency
协程的实际使用还有一些需要改进的地方。当我们使用时,
GlobalScope.launch我们创建一个顶级协程。尽管它是轻量级的,但运行时仍然会消耗一些内存资源。如果我们忘记保留对新启动的协程的引用,它仍然会运行。如果协程中的代码挂起(例如,我们错误地延迟了太长时间),如果我们启动了太多协程并耗尽了内存怎么办?必须手动保留对所有启动的协程的引用并加入它们很容易出错。有一个更好的解决方案。我们可以在代码中使用结构化并发。
GlobalScope我们可以在正在执行的操作的特定范围内启动协程,而不是像我们通常对线程所做的那样在 中启动协程(线程始终是全局的)。在我们的示例中,我们的 main 函数使用协程构建器转换为协程
runBlocking。每个协程构建器(包括runBlocking)都会将 的实例添加CoroutineScope到其代码块的范围中。我们可以在此范围内启动协程,而无需显式加入它们,因为外部协程(runBlocking在我们的示例中)只有在其范围内启动的所有协程完成后才会完成。因此,我们可以使我们的示例更简单:Run Code Online (Sandbox Code Playgroud)import kotlinx.coroutines.* fun main() = runBlocking { // this: CoroutineScope launch { // launch new coroutine in the scope of runBlocking delay(1000L) println("World!") } println("Hello,") }
因此本质上不鼓励这样做,因为它迫使您保留引用并使用join,而这可以通过结构化并发来避免。 (请参阅上面的代码示例。)本文涵盖了许多微妙之处。
| 归档时间: |
|
| 查看次数: |
2200 次 |
| 最近记录: |