【问题标题】:Whats the concept behind a CoroutineScope?CoroutineScope 背后的概念是什么?
【发布时间】:2018-11-21 13:15:27
【问题描述】:

在阅读了CoroutineScope 的介绍和javadoc 之后,我仍然有点困惑CoroutineScope 背后的想法是什么。

文档的第一句话“定义新协程的范围”。我不清楚:为什么我的协程需要范围?

另外,为什么不推荐使用独立的协程构建器?为什么这样做更好:

fun CoroutineScope.produceSquares(): ReceiveChannel<Int> = produce {
    for (x in 1..5) send(x * x)
}

而不是

fun produceSquares(): ReceiveChannel<Int> = produce { //no longer an extension function
    for (x in 1..5) send(x * x)
}

【问题讨论】:

  • 我发现这个 github 问题中的描述很有用:github.com/Kotlin/kotlinx.coroutines/issues/410。正如那里解释的那样,“......像launch { ... }async { ... }这样的协程构建器默认启动一个全局协程......这似乎是一个错误的默认值。全局协程容易出错。”现在,如果您愿意,您仍然可以通过使用GlobalScope 来执行与旧行为相同的操作,但这意味着您明确地执行此操作而不是意外执行此操作。并且建议在大多数情况下不要使用GlobalScope
  • 取消作业的子项

标签: kotlin kotlinx.coroutines


【解决方案1】:

您仍然可以通过在GlobalScope 中生成全局“独立”协程来使用它们:

GlobalScope.launch {
    println("I'm running unstructured")
}

但是,不建议这样做,因为在全局范围内创建协程基本上与我们使用旧线程所做的相同。您创建了它们,但不知何故需要跟踪引用以供以后加入/取消它们。

使用结构化并发,即在其范围内嵌套协程,您将拥有一个整体上更易于维护的系统。例如,如果您在另一个协程中生成协程,则继承外部范围。这有多个优点。如果你取消外部协程,取消将委托给它的内部协程。此外,您可以确定外部协程不会在其所有子协程完成工作之前完成。

documentation 中还显示了一个非常好的示例,用于CoroutineScope

CoroutineScope 应该在负责启动子协程的具有明确生命周期的实体上实现。 Android 上此类实体的示例是 Activity。

毕竟,您显示的produceSquares 方法的第一个版本更好,因为它只有在CoroutineScope 中调用时才能执行。这意味着您可以在任何其他协程中运行它:

launch {
    produceSquares()
}

produceSquares 内部创建的协程继承了launch 的作用域。您可以确定launch 不会在produceSquares 之前完成。此外,如果您取消了launch,这也会影响produceSquares

此外,您仍然可以像这样创建全局运行的协程:

GlobalScope.produceSquares()

但是,如上所述,在大多数情况下,这并不是最佳选择。

我还想宣传我写的一篇文章。有一些示例演示了范围的含义:https://kotlinexpertise.com/kotlin-coroutines-concurrency/

【讨论】:

  • 我仍然不确定 Scope 的实际 Job 与协程的关系是什么。它的寿命和工作处理?与实际线程的关联(我的意思是,代码最终必须由线程执行,所以有人需要以某种方式决定协程需要在哪个线程上执行)
  • 最后讲的是协程的生命周期。 CoroutineDispatcher(只是CoroutineScope 的一部分)定义了要运行的线程。例如,您可以说 launch(Dispatchers.Default) 来指定调度程序。否则,你从外部范围继承
  • @morpheus05 不要忘记同一个协程可以从一个线程传递到另一个线程,对于它将运行哪个线程没有单一的决定。当它被挂起时,它不会分配给任何线程,并且每次恢复时都会做出新的决定。您还可以使用withContext 在不暂停的情况下切换线程。最好使用的心智模型是协程运行的“衬底”是线程,就像 CPU 内核是线程运行的衬底一样。
  • 如果我查看 CoroutineScope,它是一个带有一个字段的接口,coroutineContext: CoroutineContext。但是为什么会出现这个界面呢?在作用域的生命周期中上下文可以改变吗?为什么要引入这个接口并假设我们只能使用 CoroutineContext?
  • 我自己写了一个例子(github.com/BILLyTheLiTTle/KotlinExperiments/blob/master/src/…)只是为了理解。我想知道,使用GlobalScope.launch(Dispatchers.IO) {...} 而不是CoroutineScope(Dispatchers.IO).launch {...} 或反之亦然有什么优势/劣势?
【解决方案2】:

它与结构化并发的概念有关,它定义了协程之间的结构。

On a more philosophical level,您很少像使用线程那样“全局”启动协程。协程总是与应用程序中的某个局部范围相关,这是一个生命周期有限的实体,如 UI 元素。因此,对于结构化并发,我们现在要求在 CoroutineScope 中调用启动,这是一个由生命周期受限的对象(如 UI 元素或其对应的视图模型)实现的接口。

作为这个概念的一个明显结果:cancelling scope 的上下文,它的所有子协程也将被取消。

【讨论】:

    猜你喜欢
    • 2012-04-17
    • 2011-01-15
    • 2022-01-10
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-03-20
    • 1970-01-01
    相关资源
    最近更新 更多