首页 > 解决方案 > 在 Kotlin 协程中指定调度程序/上下文有多重要?如果你不指定它们会发生什么?

问题描述

如果启动协程但未指定调度程序(例如GlobalScope.launch {}),使用什么调度程序?

如果该协程在主线程中启动,它会使用Dispatchers.main吗?

另外,如果您没有在协程中指定调度程序或上下文会发生什么?假设您进行了数据库操作,但没有指定Dispatchers.IO任何地方。这会导致任何重大问题吗?

标签: androidkotlin-coroutines

解决方案


如果启动了协程但没有指定调度程序(例如 GlobalScope.launch {}),使用什么调度程序?

如果在协程构建器中没有指定调度器,你会从CoroutineScope你启动协程的地方获取调度器。如果在该范围的上下文中没有调度器,协程将使用Dispatchers.Default(例如,参见启动文档)。

请注意,范围是协程构建器调用的接收者:

  • 如果你看到GlobalScope.launch { ... }GlobalScope是范围
  • 如果你看到scope.launch { ... }了,看看那个scope
  • 如果您launch { .. }在野外看到,则该代码段中的某些实例CoroutineScope必须可用this,因此这就是父作用域(有关其来源的示例,请参见下文)

以下是有关最常见协程作用域中使用的调度程序的一些信息:

如果作用域是GlobalScope,那么它没有任何调度程序,所以如前所述协程将使用Dispatchers.Default.

如果范围由 Android 提供lifecycleScopeviewModelScope由 Android 提供,则Dispatchers.Main.immediate默认使用协程。

如果范围是使用CoroutineScope()没有特定调度程序的工厂函数创建的,Dispatchers.Default则将使用(请参阅文档中的“自定义用法”部分)。

如果使用MainScope()和不使用特定调度程序创建范围,它将Dispatchers.Main按照相同的文档使用。

如果范围由 提供runBlocking,那么它将使用一个特殊的调度程序,它像一个事件循环一样工作,并在调用的线程中执行你的协程runBlocking(这很好,因为该线程无论如何都会被阻塞)。

如果范围由另一个(外部)协程构建器提供,例如launchor async(这意味着您的协程是该协程的子协程),则调度程序将从用于启动父协程的范围中获取,除非它们覆盖它。因此,您可以一直向上,直到达到上述选项之一:

parentScope.launch {
    // here, this = child scope that gets the dispatcher from parentScope
    
    launch {
        // inherits from parent launch
    }
    launch(Dispatchers.IO) {
        // here, this = child scope with overridden dispatcher
        launch {
            // inherits Dispatchers.IO from parent launch's scope
        }
    }
}

如果该协程在主线程中启动,它会使用 Dispatchers.main 吗?

您可能可以将其与上一段拼凑在一起,但只是重申一下:这取决于您启动协程的范围。

如果您从lifecycleScope/启动协程viewModelScope,那么是的,它将在主线程(in Dispatchers.Main.immediate)上运行。

如果你runBlocking从主线程调用,那么它也会在主线程上运行协程(但不是在Dispatchers.Main),但你绝对不应该在 Android 中这样做。

如果您使用类似的东西,MainScope()它确实会在Dispatcher.Main.

但在其他情况下,它很有可能继续运行Dispatchers.Default- 检查范围!

另外,如果您没有在协程中指定调度程序或上下文会发生什么?假设您进行了数据库操作,但没有指定Dispatchers.IO任何地方。这会导致任何重大问题吗?

正如您从这个答案的开头所看到的,使用的调度程序取决于您的协程是如何启动的。如果我们假设您没有在任何地方指定任何调度程序,那么您的协程很有可能在Dispatchers.Main(在 Android 中)或Dispatchers.Default.

Dispatchers.Main对 IO 的东西来说真的很糟糕,因为它会在 IO 发生时冻结你的 UI。我相信如果你在这个调度程序中运行错误的东西,Android 可能会崩溃,但我有一段时间没有做过 Android 开发,所以我不能肯定。

Dispatchers.Default是一个共享线程池,其大小取决于执行代码的机器的内核数量,因此它适用于 CPU 密集型任务。如果您在此调度程序中启动一堆执行阻塞 IO 的协程,您可能会阻塞所有线程并阻止其他协程运行,这确实不理想 - 它可能会导致延迟或缓慢,尤其是在您非常依赖协程的情况下。

Dispatchers.IO这并不神奇,但如果太多线程被阻塞,它会根据需要生成更多线程,以便其他协程可以运行。您仍然会产生额外线程的额外内存,但是当一些线程在 IO 上被阻塞时,其他协程将可以自由运行。

您可以在文档中阅读有关如何使用调度程序的更多信息。


推荐阅读