为什么必须将 "Dispatchers.Main" 添加到 Activity CoroutineScope 实现的根作业中?

Why must "Dispatchers.Main" be added to the root job of an implementation of an Activitys CoroutineScope?

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。如果我删除这部分,如果 Activity 被销毁, launch 协程无论如何都会被取消。那么Dispatchers.Main是什么原因呢?为什么 Dispatchers.IO 也没有添加?

从 Kotlin 1.3 开始,您需要 CoroutineScopelaunch 一个新协程。 在您的示例中,您将范围创建为 val of the activity:

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

协程范围由不同的部分组成,例如一个调度员和一份工作。 调度程序用于启动协程 - select 线程 - 作业用作从此范围创建的协程的父级。

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

launch(Dispatchers.Main) 

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

通常情况下,您会定义一个标准调度程序,它会在您的 activity 的大多数地方使用,并且只在需要时指定一个特殊的调度程序。

首先我不是Corutines专家:

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

你有一个 工作activity 生命周期Dispatchers.MainAndroid 主线程调度程序相关联,并与 UI 个对象一起运行 :

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

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

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

当你写这篇文章时:

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 时,没有任何变化。

So what is the reason for Dispatchers.Main?

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

就可以了
launch { ... }

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

Why is Dispatchers.IO not added, too?

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

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