c-a*_*-an 9 kotlin kotlin-coroutines
我正在研究CPS。我想知道它是如何工作的。
Object createPost(
Token token,
Item item,
Continuation<Post> const){...}
Run Code Online (Sandbox Code Playgroud)
interface Continuation<in T> {
val context: CoroutineContext
fun resume(value: T)
fun resumeWithException(exception: Throwable)
}
Run Code Online (Sandbox Code Playgroud)
人们说 CPS 只是回调,仅此而已。
但
<in T>
Continuation 界面中做了什么。bro*_*oot 21
最终用户视角
对于最终用户来说,情况相对简单:延续代表被挂起的执行流。resume()
它允许通过调用或来恢复执行resumeWithException()
。
例如,假设我们要暂停一秒钟然后恢复执行。我们要求协程机制挂起,它提供一个延续对象,我们存储它,稍后我们调用resume()
它。延续对象“知道”如何恢复执行:
suspend fun foo() {
println("foo:1")
val result = suspendCoroutine { cont ->
thread {
Thread.sleep(1000)
cont.resume("OK")
}
}
println("foo:2:$result")
}
Run Code Online (Sandbox Code Playgroud)
suspendCoroutine()
是暂停并获取继续以稍后恢复的可能方法之一。thread()
仅Thread.sleep()
用于演示目的 - 通常我们应该使用delay()
。
很多时候我们会停下来获取某种数据。这就是连续性支持使用结果值恢复的原因。在上面的示例中,我们可以看到 的结果suspendCoroutine()
存储为result
,并且我们通过传递 来恢复延续"OK"
。这样恢复result
持有后"OK"
。这就解释了<in T>
。
内部结构
这要复杂得多。Kotlin 在不支持协程或挂起的运行时中执行。例如,JVM 无法在不阻塞任何线程的情况下在函数内等待。这根本不可能(我在这里故意忽略 Project Loom)。为了实现这一点,Kotlin 编译器必须操作字节码,并且延续在这个过程中扮演着重要的角色。
正如您所注意到的,每个挂起函数都会接收额外的Continuation
类型参数。该对象用于控制恢复的过程,它有助于返回到函数调用者并保存当前的协程上下文。此外,挂起函数返回Any
/Object
以允许向调用者发出其状态信号。
假设我们有另一个函数调用第一个函数:
suspend fun bar() {
println("bar:1")
foo()
println("bar:2")
}
Run Code Online (Sandbox Code Playgroud)
然后我们调用bar()
. foo()
两者的字节码bar()
比您通过查看上面的源代码所预期的要复杂得多。这就是正在发生的事情:
bar()
是通过其调用者的延续来调用的(让我们暂时忽略这意味着什么)。bar()
检查它是否“拥有”传递的延续。它没有看到,所以它假设这是其调用者的延续,并且这是 的初始执行bar()
。bar()
创建自己的延续对象并将调用者的延续存储在其中。bar()
开始正常执行并到达foo()
目的。bar()
调用foo()
传递其延续。foo()
检查它是否拥有传递的延续。事实并非如此,延续由 拥有bar()
,因此foo()
创建自己的延续,bar()
将 的延续存储在其中并开始正常执行。suspendCoroutine()
并与之前类似,本地状态存储在foo()
的延续内部。foo()
在传递给 的 lambda 内提供给最终用户suspendCoroutine()
。foo()
想要暂停其执行,所以它...返回...是的,如前所述,在不阻塞线程的情况下等待是不可能的,因此释放线程的唯一方法是从函数返回。foo()
返回一个特殊值,表示:“执行已暂停”。bar()
读取这个特殊值并且也暂停,所以也立即返回。cont.resume()
.foo()
知道如何从该点继续执行suspendCoroutine()
。foo()
自身作为参数传递的函数。foo()
检查它是否拥有传递的延续 - 这次它拥有,因此它假设这不是对 的初始调用foo()
,而是恢复执行的请求。它从延续中读取存储的状态,加载局部变量并跳转到正确的代码偏移量。foo()
to返回的点bar()
。foo()
知道这次它不是由 调用的bar()
,所以简单地返回是行不通的。但它仍然保留其调用者的延续,因此在需要返回的bar()
地方暂停。foo()
foo()
返回具有神奇值的内容:“恢复我的调用者的继续”。bar()
从执行点恢复foo()
。如您所见,这非常复杂。通常,协程的用户不需要了解它们的内部工作原理。
其他重要注意事项:
foo()
不暂停,它将正常返回bar()
并bar()
继续照常执行。这是为了在不需要挂起的情况下减少整个过程的开销。CoroutineContext
,因此也存储在延续内部。 归档时间: |
|
查看次数: |
5483 次 |
最近记录: |