Day11:调度器(Dispatchers),我跳进来了,又跳出去了

Coroutine 一个重要的特性就是可以轻易的切换执行绪,不过 Coroutine 是使用 CoroutineDispatcher (调度器) 来切换执行绪,而不是直接让使用者选择不同的执行绪。因为建立执行绪是一项消耗资源的事,所以通常会建立一个执行绪池 (Thread Pool),在执行绪池中会有多个执行绪在池中等待,当任务丢进执行绪池中执行时,它会选择适当的执行绪来运行。避免在短时间内建立及销毁执行绪,造成系统的资源浪费,影响整体效能。当然除了使用执行绪池外,执行绪也可以让我们直接建立。

不管是执行绪池或者是单独的执行绪,在 Coroutine 都能够有相对应的方法支持。

Dispatchers 有分为四种:

Dispatchers.Default

Dispatchers.Main

Dispatchers.IO

Dispatchers.Unconfined


Dispatchers.Default

由於笔者是一名 Android 工程师,第一次见到 Dispatchers.Default 的时候,我以为是使用预设的执行绪,也就是主执行绪,结果跟我想的不一样。

我们知道 Coroutine 是用来处理非同步的执行,所以当需要使用 Coroutine 的时候,需要把耗时的工作丢到主执行绪以外的地方执行,所以不会是我一开始想的主执行绪。

那麽,使用 Dispatchers.Default 会使用什麽执行绪呢?

前面提到会建立一个执行绪池来执行耗时任务,而 Dispatchers.Default 就是使用背景的共享执行绪池来执行。在这个池中,执行绪的数量会等於 CPU 内核的数量,不过最少会是 2。

使用 launch、async 建立 Coroutine 时,如果没有特别指定,就会使用 Dispatchers.Default。

class Day11 {
    val scope = CoroutineScope(Job())

    fun dispatchersDefault() = scope.launch(Dispatchers.Default) {
        println("thread: ${Thread.currentThread().name}")
    }
}
fun main() = runBlocking{
    val day11 = Day11()
    day11.dispatchersDefault()
}
thread: DefaultDispatcher-worker-1
  • launch 没有带入 Dispatchers.Default 结果也会是一样。

Dispatchers.IO

如果直接将 Dispatchers.IO 带入 launch 中,我们会得到下面的结果

class Day11 {
    val scope = CoroutineScope(Job())
		...
    fun dispatchersIO() = scope.launch(Dispatchers.IO) {
        println("thread: ${Thread.currentThread().name}")
    }

}
thread: DefaultDispatcher-worker-1

原因是 Dispatchers.IO 一样是使用共享的背景执行绪池,但是由於 Dispatcher.IO 目的是提供给 IO 做使用的,所以它所建立的 worker 也会比较多 (worker 其实就是执行绪)

The number of threads used by tasks in this dispatcher is limited by the value of "kotlinx.coroutines.io.parallelism" (IO_PARALLELISM_PROPERTY_NAME) system property.It defaults to the limit of 64 threads or the number of cores (whichever is larger).

在原始码中的注解告诉我们了,它会建立 64 个执行绪或是 CPU 核心的数量 (看谁比较大)。

Dispatchers.Unconfined

Unconfined 的意思是不受限制的,什麽是不受限制的调度器呢?我们看一下下面这段程序码:

fun main() = runBlocking{
		launch(Dispatchers.Unconfined) {
        println("Unconfined      : I'm working in thread ${Thread.currentThread().name}")
        delay(500)
        println("Unconfined      : After delay in thread ${Thread.currentThread().name}")
    }
}
Unconfined      : I'm working in thread main
Unconfined      : After delay in thread kotlinx.coroutines.DefaultExecutor

在这一段 coroutine 中,一开始的执行绪是 main ,因为从 runBlocking 呼叫起来的时候是 Dispatchers.Main,当执行到 delay(500) 的时候,这个 coroutine 被暂停,等到 500 毫秒结束之後,就切换至 kotlinx.coroutines.DefaultExecutor 执行绪。

通常用在拦截在非 suspend API 或从阻塞的世界中调用 coroutine 相关的程序。 Ref

Dispatchers.Main

最後是我们的 Dispatchers.Main,还记得在文章最前面提到我搞错 Dispatchers.Default 是执行在主执行绪上吗?真正执行在主执行绪上的是 Dispatchers.Main。

在做完耗时的任务之後,最後如果需要更新画面,我们就必须要把执行绪切回主执行绪上。

@JvmStatic
public actual val Main: MainCoroutineDispatcher get() = MainDispatcherLoader.dispatcher

因为在不同平台上,主执行绪的使用都各有不同,所以在这边会根据不同平台来取得相对应的 dispatchers。

On JS and Native it is equivalent of Default dispatcher.
On JVM it is either Android main thread dispatcher, JavaFx or Swing EDT dispatcher. It is chosen by ServiceLoader.

我们可以使用 withContext 在 coroutine 中把执行绪切回主执行绪,如下:

launch {
    doSomething()
        withContext(Dispatchers.Main){
            updateUI()
        }
}

小结

Coroutine 在背景其实还是使用执行绪在做耗时任务,只不过 Coroutine 能够让非同步的程序码更容易写,正确使用 Dispatchers 能够让我们的 coroutine 执行在正确的执行绪上。

参考资料

Kotlin Coroutines Dispatchers 那一两件事
Coroutine context and dispatchers | Kotlin
CoroutineDispatcher

特别感谢

Kotlin Taiwan User Group
Kotlin 读书会


<<:  Day9-"格式化符号"

>>:  Day7 开始使用Git

OpenStack Neutron 介绍 1

本系列文章同步发布於笔者网站 昨天为读者介绍目前 OpenStack 中算是最核心的元件,Keyst...

Day22-JDK可视化监控工具:jconsole(二)

前言 延续着上篇(Day21-JDK可视化监控工具:jconsole(一))的jconsole介绍,...

好用进销存系统EZTOOL

我们是一间批发机械进口商,开业时间约2年,一直以来就只有一位行政人员。近期实在认为需要软件来管理公司...

【Day 06】LeetCode:Two Sum ( 用 JavaScript 学演算法 )

我们继续透过 LeetCode #1 Two Sum 来实际感受解决问题的过程 ( 题目连结 ) 一...

[DAY 27] 章节3-7: 对立的鸡群们- k-means(k平均分类演算法) (1/2)

3-7 对立的鸡群们 在飞哥的工作室也待好一阵子的小博,这天在网路上收集资料,以便帮飞哥在报告上有更...