在 Kotlin 协程中指定调度程序/上下文有多重要?如果您不指定它们会发生什么?

Sma*_*mer 8 android kotlin-coroutines

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

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

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

Jof*_*rey 12

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

如果在调用协程构建器(launchasync等)时未指定调度程序,则您可以从启动协程的位置获取调度程序CoroutineScope。如果该作用域的上下文中没有调度程序,则协程将使用Dispatchers.Default(请参阅例如启动)。

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

  • 如果你看到GlobalScope.launch { ... }那么GlobalScope就是范围
  • 如果你看到了scope.launch { ... },看看那个scope
  • 如果您看到没有接收器,则必须像该代码段一样提供launch { .. }某些实例,这就是范围(请参阅下面的示例,了解它可能来自何处)CoroutineScopethis

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

如果作用域是GlobalScope,那么它没有任何调度程序,因此正如之前提到的,协程将使用Dispatchers.Default

如果范围是AndroidlifecycleScopeviewModelScope由 Android 提供,则Dispatchers.Main.immediate默认情况下将使用协程。

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

如果作用域是使用CoroutineScope(someContext)工厂函数创建的,并且someContext包含调度程序,则该调度程序将用于启动协程。

如果范围是使用特定调度程序创建的,并且没有使用特定调度程序,则它将按照相同的文档MainScope()使用。Dispatchers.Main

如果作用域是使用MainScope(someContext)工厂函数创建的,并且someContext包含调度程序,则该调度程序将用于启动协程。

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

如果范围由 提供runBlocking(someContext) { ... },并且someContext包含调度程序,则子协程将使用该调度程序。

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

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
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

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

您可能可以将其与上一段结合起来,但重申一下:这取决于您启动协程的范围。

lifecycleScope如果您从/启动协程viewModelScope,那么它将在主线程上运行(在 中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 上被阻塞。

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