但是暂停是什么意思呢?
用suspend关键字标记的函数在编译时被转换为异步的(在字节码中),即使它们在源代码中看起来是同步的。
在我看来,理解这种转变的最好来源是Roman Elizarov的“深入协作程序”。
例如,下面的函数:
class MyClass {
suspend fun myFunction(arg: Int): String {
delay(100)
return "bob"
}
}
转换为以下(为简单起见,用Java而不是实际的JVM字节码表示):
public final class MyClass {
public final Object myFunction(int arg, @NotNull Continuation<? super String> $completion) {
// ...
}
}
这包括对函数的以下更改:
The return type is changed to Java's Object (the equivalent of Kotlin's Any? - a type containing all values), to allow returning a special COROUTINE_SUSPENDED token to represent when the coroutine is actually suspended
It gets an additional Continuation<X> argument (where X is the former return type of the function that was declared in the code - in the example it's String). This continuation acts like a callback when resuming the suspend function.
Its body is turned into a state machine (instead of literally using callbacks, for efficiency). This is done by breaking down the body of the function into parts around so called suspension points, and turning those parts into the branches of a big switch. The state about the local variables and where we are in the switch is stored inside the Continuation object.
这是一种非常快速的描述方式,但是你可以在演讲中看到更多的细节和例子。整个转换基本上就是“挂起/恢复”机制在底层的实现方式。
协程或函数被挂起?
在高层次上,我们说调用挂起函数挂起协程,这意味着当前线程可以开始执行另一个协程。因此,协程被挂起,而不是函数被挂起。
事实上,由于这个原因,暂停函数的调用点被称为“暂停点”。
哪个协程被挂起?
让我们看看你的代码,并分解发生了什么(编号遵循执行时间轴):
// 1. this call starts a new coroutine (let's call it C1).
// If there were code after it, it would be executed concurrently with
// the body of this async
async {
...
// 2. this is a regular function call, so we go to computation()'s body
val deferred = computation()
// 4. because await() is suspendING, it suspends coroutine C1.
// This means that if we had a single thread in our dispatcher,
// it would now be free to go execute C2
// 7. once C2 completes, C1 is resumed with the result `true` of C2's async
val result = deferred.await()
...
// 8. C1 can now keep going in the current thread until it gets
// suspended again (or not)
}
fun computation(): Deferred<Boolean> {
// 3. this async call starts a second coroutine (C2). Depending on the
// dispatcher you're using, you may have one or more threads.
// 3.a. If you have multiple threads, the block of this async could be
// executed in parallel of C1 in another thread
// 3.b. If you have only one thread, the block is sort of "queued" but
// not executed right away (as in an event loop)
//
// In both cases, we say that this block executes "concurrently"
// with C1, and computation() immediately returns the Deferred
// instance to its caller (unless a special dispatcher or
// coroutine start argument is used, but let's keep it simple).
return async {
// 5. this may now be executed
true
// 6. C2 is now completed, so the thread can go back to executing
// another coroutine (e.g. C1 here)
}
}
外部异步启动一个协程。当它调用computation()时,内部异步程序启动第二个协程。然后,await()调用挂起外部异步协程的执行,直到内部异步协程的执行结束。
You can even see that with a single thread: the thread will execute the outer async's beginning, then call computation() and reach the inner async. At this point, the body of the inner async is skipped, and the thread continues executing the outer async until it reaches await().
await() is a "suspension point", because await is a suspending function.
This means that the outer coroutine is suspended, and thus the thread starts executing the inner one. When it is done, it comes back to execute the end of the outer async.
挂起是否意味着当外部异步协程正在等待(await)内部计算协程完成时,它(外部异步协程)空闲(因此称为挂起)并返回线程池,当子计算协程完成时,它(外部异步协程)醒来,从池中取出另一个线程并继续?
是的,准确。
实现这一点的实际方法是将每个挂起函数转换为一个状态机,其中每个“状态”对应于这个挂起函数中的一个挂起点。在底层,函数可以被多次调用,并带有关于它应该从哪个挂起点开始执行的信息(您应该真正地观看我链接的视频以获得有关这方面的更多信息)。