Kotlin 协程的 Unconfined

255 阅读2分钟

1.jpg

在之前的一篇文章中,我们讨论了 Kotlin 协程中 IODefault 的,细心的读者可能会发现,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_washtask_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 中执行后续代码了。

我们用一张图表示这个逻辑:

2.png

什么时候用 Dispatchers.Unconfined?老实说,我也不知道。

从功能上看,如果你不想提供线程池供协程运行,只希望协程的代码在挂起函数的恢复线程中执行的话,Dispatchers.Unconfined 就是你的不二选择。

CoroutineStart.UNDISPATCHED

若需在协程恢复后将其约束至特定线程或线程池,同时仍希望其在当前调用帧内立即执行直至首次挂起,可通过在 launchasync 等协程构建器中设置可选参数 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 中执行了。

一张图表示该逻辑:

3.png