在之前的一篇文章中,我们讨论了 Kotlin 协程中 IO 和 Default 的,细心的读者可能会发现,Kotlin 中还有一个 Dispatchers.Unconfined——这个又有什么用呢?
好,今天,我们就讲述一下 Dispatchers.Unconfined 的神奇之处。
Dispatchers.Unconfined
我们先来写一段测试代码:
fun main() = runBlocking<Unit> {
launch(context = Dispatchers.IO) {
println(Thread.currentThread().name)
task_wash()
println(Thread.currentThread().name)
task_cooking()
println(Thread.currentThread().name)
}
}
suspend fun task_wash() = suspendCoroutine { continuation ->
thread {
println("Cooking In ${Thread.currentThread().name}")
Thread.sleep(1000)
continuation.resume(Unit)
}
}
suspend fun task_cooking() = suspendCoroutine { continuation ->
thread {
println("Cooking In ${Thread.currentThread().name}")
Thread.sleep(1000)
continuation.resume(Unit)
}
}
我们定义了两个任务 task_wash 和 task_cooking,他们分别在自己的独立线程中,完成给任务。
通过使用 Dispatchers.IO 启动协程,我们可以让协程的主体在 Dispatchers.IO 中运行,上述代码的输出如下:
// Output
DefaultDispatcher-worker-1
Cooking In Thread-3
DefaultDispatcher-worker-1
Cooking In Thread-4
DefaultDispatcher-worker-1
这个和我们预想的一样,协程一开始就完全在 Dispatchers.IO 中运行。
当我们使用 Dispatchers.Unconfined 时,情况会有所不同。
fun main() = runBlocking<Unit> {
launch(context = Dispatchers.Unconfined)
//...
}
输出如下:
// Output
main
Cooking In Thread-0
Thread-0
Cooking In Thread-1
Thread-1
你会发现,协程的主体一开始是在 main 中执行的,但是当执行 task_wash 之后,变成了 Thread-0 中执行,在执行 task_cooking 之后,又在 Thread-1 中执行了。
没错,这正是 Dispatchers.Unconfined 的工作方式。
Dispatchers.Unconfined 是一种无线程约束的协程调度器。它在当前调用帧内执行协程的初始代码,并允许协程在任意挂起函数使用的线程中恢复运行,无需遵循特定线程策略。
一句话总结:Dispatchers.Unconfined 不提供运行协程的线程池,协程在什么地方恢复,就在哪个线程中执行后续代码。
所以在上述例子中,才有这样的效果:执行 task_wash 之后,便在 task_wash 的线程 Thread-0 中执行后续代码,在执行 task_cooking 之后,又在 task_cooking 的线程 Thread-1 中执行后续代码了。
我们用一张图表示这个逻辑:
什么时候用 Dispatchers.Unconfined?老实说,我也不知道。
从功能上看,如果你不想提供线程池供协程运行,只希望协程的代码在挂起函数的恢复线程中执行的话,Dispatchers.Unconfined 就是你的不二选择。
CoroutineStart.UNDISPATCHED
若需在协程恢复后将其约束至特定线程或线程池,同时仍希望其在当前调用帧内立即执行直至首次挂起,可通过在 launch 或 async 等协程构建器中设置可选参数 CoroutineStart.UNDISPATCHED 实现。
上述代码我们稍作修改:
//...
launch(start = CoroutineStart.UNDISPATCHED, context = Dispatchers.IO)
//...
我们设置协程的启动方式为 CoroutineStart.UNDISPATCHED,并期望协程在 Dispatchers.IO 中运行。
看下输出结果:
// Output
main
Cooking In Thread-0
DefaultDispatcher-worker-1
Cooking In Thread-4
DefaultDispatcher-worker-1
你会发现,协程开始执行的时候,是在 main 线程中执行的,在完成第一个挂起函数执行之后,协程便在 Dispatchers.IO 中执行了。
一张图表示该逻辑: