【问题标题】:koltin, what's difference from using CoroutineScope directly and derive the class from CoroutineScopekotlin,与直接使用 CoroutineS 并从 CoroutineScope 派生类有什么区别
【发布时间】:2020-11-21 13:23:07
【问题描述】:

在启动协程时,它可能只创建一个 CoroutineScope 并从中调用 launch{} -- doSomething_2()

或从 CoroutineScope 派生类并使用要启动的类{}。 --doSomething_1()

这两种方式有区别吗,首选哪种方式?

class AClass : CoroutineScope {

    override val coroutineContext: CoroutineContext = Dispatchers.Main
    
    var theJob1: Job? = null
    var theJob2: Job? = null
    
    fun doSomething_1() {
        theJob1 = launch(Dispatchers.IO) {
            // ... ...
        }
    }
    
    fun doSomething_2() {
        theJob2 = CoroutineScope(Dispatchers.IO).launch {
            // ... ...
        }
    }
    
    fun dispose() {
        theJob1?.cancel()
        theJob2?.cancel()
    }
}

【问题讨论】:

    标签: kotlin-coroutines coroutinescope


    【解决方案1】:

    这两种方式有区别吗,首选哪种方式?

    是的,有一个根本的区别,即一个正确,另一个不正确。它与结构化并发有关:如果您的 AClass 是您的“工作单元”的根对象,无论它是什么,并且负责(或观察者)其生命周期,那么它也应该是根范围您将在其中启动的协程。当生命周期结束时,AClass 应该通过在自身上调用 cancel 来将该事件传播到协程子系统,从而取消根范围。 CoroutineScope.cancel 是一个扩展函数。

    我采用了您的代码并进行了以下修复:

    1. CoroutineScope.coroutineContext 里面必须有一个Job(),所以我添加了它。我删除了调度程序,因为它与这个故事无关,而 Main 调度程序用于 GUI,而我们正在运行一个简单的测试。

    2. 我删除了你的dispose() 功能,我们有现成的cancel()

    3. 我删除了 theJob1theJob2 字段,因为一旦您开始正确使用结构化并发,它们就毫无用处。

    我还添加了一些代码,可以让我们观察行为:

    1. 在每个协程中添加了 delayprintln 以查看何时完成。

    2. 添加了一个main 函数来测试它。该函数在最后一行永远阻塞,以便我们可以看到启动的协程将做什么。

    代码如下:

    import kotlinx.coroutines.*
    import java.lang.Thread.currentThread
    import kotlin.coroutines.CoroutineContext
    
    fun main() {
        val a = AClass()
        a.doSomething_1()
        a.doSomething_2()
        a.cancel()
        currentThread().join()
    }
    
    class AClass : CoroutineScope {
    
        override val coroutineContext: CoroutineContext = Job()
    
        fun doSomething_1() {
            launch(Dispatchers.IO) {
                try {
                    delay(10_000)
                } finally {
                    println("theJob1 completing")
                }
            }
        }
    
        fun doSomething_2() {
            CoroutineScope(Dispatchers.IO).launch {
                try {
                    delay(10_000)
                } finally {
                    println("theJob2 completing")
                }
            }
        }
    }
    

    当你运行它时,你会看到只有 theJob1 完成,而 theJob2 运行了整整 10 秒,不服从 cancel 信号。

    这是因为构造 CoroutineScope(Dispatchers.IO) 创建了一个独立的范围,而不是成为您的 AClass 范围的子范围,从而破坏了协程层次结构。

    理论上,您仍然可以使用显式的CoroutineScope 构造函数来保持层次结构,但是您会得到一些显然不是首选方式的东西:

    CoroutineScope(coroutineContext + Dispatchers.IO).launch {
    

    这相当于只是

    launch(Dispatchers.IO) {
    

    【讨论】:

      【解决方案2】:

      两个协程将以相同的上下文启动。您可以通过在两者中打印协程上下文来看到这一点:

      launch(Dispatchers.IO) {
          println("doSomething_1 context: ${coroutineContext}")
      }
      CoroutineScope(Dispatchers.IO).launch {
          println("doSomething_2 context: ${coroutineContext}")
      }
      

      这将打印如下内容:

      doSomething_1 context: [StandaloneCoroutine{Active}@7b8cce78, Dispatchers.IO]
      doSomething_2 context: [StandaloneCoroutine{Active}@3c938006, Dispatchers.IO]
      

      我还没有看到CoroutineScope 经常在内部协程代码之外实现。在这种情况下,您应该更喜欢组合而不是继承,特别是因为 CoroutineContext 在其核心是可组合的,使用 + 运算符。例如,当您 launch 一个新的协程时,现有的上下文只是简单地与您提供的新上下文相结合。

      进一步阅读:

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2019-11-13
        • 2020-04-09
        • 2022-11-12
        • 1970-01-01
        • 2021-01-02
        • 2023-01-22
        • 2021-11-05
        • 1970-01-01
        相关资源
        最近更新 更多