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



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

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

此外,如果您没有在协同程序中指定调度器或上下文,会发生什么?假设您执行了数据库操作,但没有在任何位置指定Dispatchers.IO。这会引起什么重大问题吗?

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

如果在调用协程生成器(launchasync等)时没有指定调度程序,则可以从启动协程的CoroutineScope中获取调度程序。如果该范围的上下文中没有调度程序,协程将使用Dispatchers.Default(例如,请参阅启动文档)。

注意,作用域是协同程序生成器调用的接收器

  • 如果您看到GlobalScope.launch { ... },那么GlobalScope就是作用域
  • 如果你看到scope.launch { ... },看看那个scope
  • 如果您看到没有接收器的launch { .. },那么CoroutineScope的某个实例在该代码中必须作为this可用,这就是作用域(有关它可能来自哪里的示例,请参见下面)

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

如果作用域是GlobalScope,那么它就没有任何调度器,所以正如前面提到的,协程将使用Dispatchers.Default

如果作用域是Android提供的lifecycleScopeviewModelScope,则协同程序默认使用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
}
}
}

如果协同程序是在主线程中启动的,它会使用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上被阻塞时,其他协同程序将可以自由运行

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

相关内容

最新更新