【问题标题】:Why must "Dispatchers.Main" be added to the root job of an implementation of an Activitys CoroutineScope?为什么必须将“Dispatchers.Main”添加到 Activity CoroutineScope 实现的根作业中?
【发布时间】:2018-11-15 16:56:43
【问题描述】:
abstract class ScopedAppActivity: AppCompatActivity(), CoroutineScope {
    protected lateinit var job: Job
    override val coroutineContext: CoroutineContext 
        get() = job + Dispatchers.Main

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        job = Job()

        launch(Dispatchers.Main) {
            try {
                delay(Long.MAX_VALUE)
            } catch (e: Exception) {
                // e will be a JobCancellationException if the activty is destroyed
            }
        }
    }

    override fun onDestroy() {
        super.onDestroy()
        job.cancel()
    } 
}

此示例复制自 coroutine guide 并由 launch(Dispatchers.Main) 协程扩展。我不明白为什么需要第 4 行中的+ Dispatchers.Main。如果我删除这部分,launch 协程无论如何都会在 Activity 被销毁的情况下被取消。那么Dispatchers.Main的原因是什么?为什么Dispatchers.IO也没有加?

【问题讨论】:

    标签: android kotlin kotlin-coroutines


    【解决方案1】:

    当你写这篇文章时:

    launch(Dispatchers.Main) {
        try {
            delay(Long.MAX_VALUE)
        } catch (e: Exception) {
            // e will be a JobCancellationException if the activty is destroyed
        }
    }
    

    您可能没有意识到 launch 实际上是用您的 ScopedAppActivity 作为接收者来调用的。所以你有效地写了

    this.launch(Dispatchers.Main) { ... }
    

    launchCoroutineScope 的扩展函数,它将使用它的coroutineContext 作为起点,将它与括号中指定的任何内容结合起来。所以,在你的情况下,有效的上下文是

    job + Dispatchers.Main + Dispatchers.Main
    

    你可以想象,这等于

    job + Dispatchers.Main
    

    因此,当您从 coroutineContext 中删除 Dispatchers.Main 时,没有任何变化。

    那么 Dispatchers.Main 的原因是什么?

    coroutineContext中提供Dispatchers.Main的好处是不用每次都提供,直接写就行了

    launch { ... }
    

    launch 中的块将保留在 GUI 线程上,这是在 Android 和其他 GUI 应用程序上使用协程最自然的方式。

    为什么也没有添加 Dispatchers.IO?

    由于该行不是要声明您将使用的所有调度程序,而是默认一个,因此提供多个调度程序是没有意义的。

    在另一个层面上,CoroutineContext 不是一个列表(+ 运算符暗示了这一点),而是一个映射。 + 语法之所以有效,是因为您添加的每个对象都声明了自己的映射键,+ 使用该键将其放入上下文的内部映射中。所以实际上不可能将两个调度员合二为一CoroutineContext

    【讨论】:

    • 是否可以删除 protected lateinit var job: Job 并简单地将其替换为 override val coroutineContext: CoroutineContext = Job() + Dispatchers.Main 并在 onDestroy 中进一步替换为 coroutineContext.cancel()
    • 是的,我相信这会奏效。唯一改变它的是Job() 在调用onCreate() 之前就在那里,因此您可以在活动到达create 生命周期点之前启动协程。我认为这更多的是理论上的区别。
    【解决方案2】:

    从 Kotlin 1.3 开始,您需要 CoroutineScopelaunch 一个新的协程。 在您的示例中,您创建一个范围为活动的val

    override val coroutineContext: CoroutineContext 
        get() = job + Dispatchers.Main
    

    协程作用域由不同的部分组成,例如调度员和工作。 调度程序用于启动协程 - 选择线程 - 作业用作从该范围创建的协程的父级。

    如果您在调用launch 方法时指定另一个调度程序,该调度程序将覆盖标准调度程序:

    launch(Dispatchers.Main) 
    

    在您的示例中,给定的 Dispatchers.Main 会覆盖标准的 Dispatchers.Main - 没有任何反应。

    通常您会定义一个在活动的大多数地方都使用的标准调度程序,并且仅在需要时指定一个特殊的调度程序。

    【讨论】:

      【解决方案3】:

      首先我不是协程专家:

      第一个问题:我不明白为什么需要第 4 行中的 + Dispatchers.Main。如果我删除这部分,那么如果 Activity 被销毁,启动协程无论如何都会被取消。那么 Dispatchers.Main 的原因是什么?

      你有一个 Jobactivity lifeCycleDispatchers.Main 相关联,该 Dispatchers.MainAndroid 主线程调度程序和使用 UI 对象进行操作

      看起来很整洁。如果您的活动正在破坏,您将取消您的工作,如果您的主线程结束(例如发生异常),您将取消您的工作。

      第二个问题:为什么也没有添加 Dispatchers.IO?

      在这种情况下,更改为位于应用程序主线程上的另一个线程是没有意义的,因为活动位于主线程中

      【讨论】:

        猜你喜欢
        • 2022-11-12
        • 2016-01-18
        • 1970-01-01
        • 2019-09-18
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多