Kotlin 为什么不使用GlobalScope.launch?

Kotlin 为什么不使用GlobalScope.launch?,kotlin,kotlinx.coroutines,jvm-languages,Kotlin,Kotlinx.coroutines,Jvm Languages,我读到,使用Globalscope是非常不鼓励的 我有一个简单的用例。对于我收到的每一条kafka消息(比如ID列表),我都必须拆分它并同时调用rest服务,等待它完成并继续执行其他同步任务。该应用程序中没有任何其他内容需要协同路由。在这种情况下,我能逍遥法外吗 注意:这不是android应用程序。它只是一个运行在服务器端的kafka流处理器。它是一个短暂的、无状态的、容器化(Docker)应用程序,运行在Kubernetes(如果您愿意的话,符合Buzzword)中,由文档使用异步或在Glob

我读到,使用
Globalscope
是非常不鼓励的

我有一个简单的用例。对于我收到的每一条kafka消息(比如ID列表),我都必须拆分它并同时调用rest服务,等待它完成并继续执行其他同步任务。该应用程序中没有任何其他内容需要协同路由。在这种情况下,我能逍遥法外吗


注意:这不是android应用程序。它只是一个运行在服务器端的kafka流处理器。它是一个短暂的、无状态的、容器化(Docker)应用程序,运行在Kubernetes(如果您愿意的话,符合Buzzword)

中,由文档使用异步或在
GlobalScope
实例上启动是非常不受欢迎的,应用程序代码通常应该使用应用程序定义的
CoroutineScope

如果我们查看
GlobalScope
的定义,我们将看到它被声明为对象

object GlobalScope : CoroutineScope { ... }
对象表示一个单个静态实例(单例)。在Kotlin/JVM中,当JVM加载一个类时,一个静态变量就存在了,而当该类被卸载时,这个静态变量就消失了。当您首次使用
GlobalScope
时,它将被加载到内存中,并一直保持在内存中,直到发生以下情况之一:

  • 该类已卸载
  • JVM关闭
  • 这个过程结束了
  • 因此,在服务器应用程序运行时,它将消耗一些内存。 即使您的服务器应用程序已完成运行,但进程未被破坏,启动的协同程序也可能仍在运行并消耗内存

    使用
    GlobalScope.async
    GlobalScope.launch
    从全局范围启动新的协同路由将创建顶级的“独立的”协同路由

    提供协同路由结构的机制称为结构化并发。让我们看看结构化并发比全局范围有什么好处:

    object GlobalScope : CoroutineScope { ... }
    
    • 作用域通常负责子协同程序,其生存期附加到作用域的生存期
    • 如果出现问题,或者用户只是改变了主意并决定继续,则作用域可以自动取消子协同路由 撤消该操作
    • 作用域自动等待所有子协同路由的完成。因此,如果作用域对应于一个协同程序,那么 父协同程序在所有协同程序完成之前不会完成 在其范围内推出的都是完整的
    使用
    GlobalScope.async
    时,没有将多个协同路由绑定到较小的范围的结构。从全局范围开始的协同程序都是独立的;它们的生存期仅受整个应用程序的生存期的限制。可以存储从全局作用域开始的对协同程序的引用,并等待其完成或显式取消,但它不会像结构化的引用那样自动发生。如果我们想取消作用域中的所有协同路由,使用结构化并发,我们只需要取消父协同路由,这会自动将取消传播到所有子协同路由

    如果您不需要将协同路由范围限定到特定的生存期对象,并且希望启动一个在整个应用程序生存期内运行且不会过早取消的顶级独立协同路由,并且您不希望使用结构化并发的好处,然后继续在您的it状态中使用全局范围

    应用程序代码通常应该使用应用程序定义的
    CoroutineScope
    ,在
    GlobalScope
    他非常气馁

    我的回答是针对这一点的

    一般来说,
    GlobalScope
    可能是个坏主意,因为它不受任何作业的约束。您应将其用于以下用途:

    全局作用域用于启动顶级协同程序,这些程序是 在整个应用程序生命周期内运行,并且不会被取消 过早地

    这似乎不是你的用例


    欲了解更多信息,请访问官方文件中的一段

    在实际应用中,仍然有一些需要改进的地方 协同程序。当我们使用
    GlobalScope.launch
    时,我们创建了一个顶级 协同程序。尽管它很轻,但它仍然消耗一些能量 内存资源,而它运行。如果我们忘了记录 新推出的协同程序仍在运行。如果数据库中的代码 协同程序挂起(例如,我们错误地延迟了太长时间),什么 如果我们启动了太多的协同程序并且内存不足?不得不 手动保留对所有已启动协同路由的引用并加入它们 它容易出错

    有更好的解决办法。我们可以在应用程序中使用结构化并发 代码。而不是像我们一样在
    GlobalScope
    中启动协同路由 通常使用线程(线程总是全局的),我们可以启动 我们正在执行的操作的特定范围内的协同程序

    在我们的示例中,我们将main函数转换为协同程序 使用
    runBlocking
    coroutinebuilder。每一个协同程序构建者, 包括
    runBlocking
    ,将
    CoroutineScope
    的实例添加到范围中 它的代码块。我们可以在此范围内启动协同路由,而无需 必须显式地加入它们,因为外部协同程序 (
    runBlocking
    在我们的示例中)在所有 在其范围内启动的协同程序已完成。因此,我们可以 示例:

    import kotlinx.coroutines.*
    
    fun main() = runBlocking { // this: CoroutineScope
        launch { // launch new coroutine in the scope of runBlocking   
            delay(1000L)   
            println("World!")    
        }   
        println("Hello,")  
    }
    
    所以本质上它是不被鼓励的,因为它迫使您保留引用并使用
    joinsuspend 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) }
        }
    }
    
    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) }
        }
    }
    
    private fun startGlobalThread() {
        GlobalScope.launch {
            var count = 0
            while (true) {
                try {
                    delay(100)
                    println("Logging some Data")
                }catch (exception: Exception) {
                    println("Global Exception")
                }
            }
        }
    }
    
    /**
     * Don't use another coroutine inside GlobalScope
     * DB update may fail while updating
     */
    private fun fireAndForgetDBUpdate() {
        GlobalScope.launch {
            val someProcessedData = ...
            db.update(someProcessedData)
        }
    }