协程与您描述的任何调度策略完全不同。协程基本上是suspend funs 的调用链。暂停完全由您控制:您只需致电suspendCoroutine。您将获得一个回调对象,因此您可以调用其resume 方法并返回您暂停的位置。
这里有一些代码,你可以看到暂停是一个非常直接和透明的机制,完全在你的控制之下:
import kotlin.coroutines.*
import kotlinx.coroutines.*
var continuation: Continuation<String>? = null
fun main(args: Array<String>) {
val job = GlobalScope.launch(Dispatchers.Unconfined) {
while (true) {
println(suspendHere())
}
}
continuation!!.resume("Resumed first time")
continuation!!.resume("Resumed second time")
}
suspend fun suspendHere() = suspendCancellableCoroutine<String> {
continuation = it
}
上面的所有代码都在同一个主线程上执行。根本没有多线程。
你launch 的协程每次调用suspendHere() 时都会挂起。它将延续回调写入continuation 属性,然后您显式使用该延续来恢复协程。
代码使用Unconfined 协程调度器,它根本不分派到线程,它只是在您调用continuation.resume() 的地方运行协程代码。
考虑到这一点,让我们重新审视您的图表:
GlobalScope.launch { <---- (A)
val y = loadData() <---- (B) // suspend fun loadData()
println(y) <---- (C)
delay(1000) <---- (D)
println("completed") <---- (E)
}
- Kotlin 在开头有一个预定义的
ThreadPool。
它可能有也可能没有线程池。 UI 调度程序与单个线程一起工作。
线程成为协程调度器目标的先决条件是有一个与之关联的并发队列,并且线程运行一个顶级循环,该循环从该队列中获取Runnable对象并执行它们。协程调度器只是将延续放在该队列中。
- 在
(A),Kotlin 开始在下一个可用的空闲线程(比如Thread01)中执行协程。
它也可以是你调用launch的同一个线程。
- 在
(B),Kotlin 停止执行当前线程,并在下一个可用的空闲线程 (Thread02) 中启动挂起函数 loadData()。
Kotlin 不需要为了挂起协程而停止任何线程。事实上,协程的主要观点是线程不会启动或停止。线程的顶级循环将继续并选择另一个 runnable 来运行。
此外,您调用suspend fun 的事实本身没有任何意义。协程只有在显式调用suspendCoroutine 时才会挂起。该函数也可以简单地返回而不暂停。
但我们假设它确实调用了suspendCoroutine。在这种情况下,协程不再在任何线程上运行。它被暂停并且无法继续,直到某个代码在某处调用continuation.resume()。该代码可以在未来任何时间在任何线程上运行。
- 当
(B) 执行后返回时,Kotlin 在下一个可用的空闲线程中(Thread03)继续协程。
B 不会“执行后返回”,协程在其主体内继续执行。它可以在返回之前暂停和恢复任意次数。
-
(C) 在 Thread03 上执行。
- 在
(D),Thread03 停止。
- 1000 毫秒后,
(E) 在下一个空闲线程上执行,例如 Thread01。
再一次,没有线程被停止。协程被挂起,并且通常特定于调度程序的机制用于安排其在 1000 毫秒后恢复。此时它将被添加到与调度程序关联的运行队列中。
为了具体起见,让我们看一些示例,说明调度协程需要什么样的代码。
Swing UI 调度程序:
EventQueue.invokeLater { continuation.resume(value) }
Android UI 调度程序:
mainHandler.post { continuation.resume(value) }
ExecutorService 调度器:
executor.submit { continuation.resume(value) }