一、前言
协程不是进程或线程,其执行过程更类似于子例程(函数属其一种)
——百度百科
协程的概念很早就有了,一些相关的历史我就不必赘言,直接进入主题吧。Kotlin 的协程至 V1.3 版本 就已经成熟完善,而其十分便捷的 API 操作起来十分顺手,在 Android 中使用起来得心应手……
对于如此强大的利器,我觉得有必要应该去了解一下其原理及其实现过程,一方面可以更加丝滑的使用协程,让我们面对不同的业务,可以作出更优的选择;另一方面就是提升自己的软实力。
如标题所见,本章将会对协程的创建过程和执行过程来进行讲解,希望能够帮助大家理解 Kotlin 的协程。
对于如何开始呢?我们还是遵循之前的套路……从最朴实无华的代码开始吧……
本系列文章源码均为 Kotlin Version 1.4.0
二、示例
创建一个协程,我们需要用到 suspend 函数。
1、普通函数和suspend函数的创建过程
在 Kotlin 中,我们可以直接在代码块创建函数:
fun main(){
//普通函数
val function1 = fun(){
}
//普通带参函数
val function2 = fun(str:String){
println(str)
}
}
上面就是普通函数的创建过程,而 suspend 函数就需要借助 Kotlin 的 API :
//Suspend.kt
public inline fun <R> suspend(noinline block: suspend () -> R): suspend () -> R = block
根据这个 API 我们大概可以知道,是不能直接创建带参的 suspend 函数 ,而且关于 suspend 函数 有这么一个规矩:suspend 函数 能够调用普通函数和 suspend 函数,而普通函数是不能调用 suspend函数。那该怎么办呢?
答案是:我们可以通过普通函数传参,返回(是返回不是调用) 一个 suspend函数。
fun main(){
//普通挂起函数
val suspendFun1 = suspend{
}
//带参数的挂起函数(抽象版)
val suspendFun2 = fun(str:String) = suspend {
println(str)
}
//带参数的挂起函数(便于理解版)
val suspendFun3 = createSuspendWithParamAndExe("hello")
}
//带参数的挂起函数(便于理解版)
fun createSuspendWithParam(str:String) = suspend {
prinln(str)
}
2、协程的创建和执行
fun main(){
//1. 先创建一个 suspend 函数
val suspendFun = suspend {
println("做些什么……")
"hello" //注意,这个是 suspend 函数的返回值!!!
}
//2. 创建协程
val coroutine = suspendFun.createCoroutine(object :Continuation<String>{
override val context: CoroutineContext = EmptyCoroutineContext
override fun resumeWith(result: Result<String>) {
println("resume:${
result.getOrNull()}")
}
})
//3. 执行协程
coroutine.resume(Unit)
}
那么这一整章就会围绕这三个过程来进行分析!
三、资料
注意!
注意!
注意!
一定要参考下方的图!(建议下载PDF文件,会更加清晰)
PDF 链接:
百度网盘:
链接: https://pan.baidu.com/s/1jv934letMgj73Ci5YqO1ZQ
提取码: t88y
CSDN:https://download.csdn.net/download/catzifeng/12782877
四、协程的创建过程
看到图,一切都是从 左上角 开始的。而协程的创建过程是从 红的的数字 开始的。
①. 这是个 suspend 函数 的扩展函数,用于创建协程的。传入的是一个 Continuation
类型的参数,而返回的也是 Continuation
类型。看参数的名字 completion
译为:完成。那么我就可以很直白的告诉大家,这个就是作为回调函数的存在——接收 suspend 函数 执行后的结果。 前面我们也反复提到 suspend 函数 是有返回的,如果你确实不需要什么,那也必须返回 Unit。
②. 这是 SafeContinuation 类,对于协程的创建过程,我们不需要仔细的阅读其代码。直接看到它的第一个构造参数,是一个名为 delegate
的 Continuation 类型,看到这里我们基本就可以猜测:SafeContinuation 只做表面功夫,真正的实现还得看这个 delegate
,所以我们看看 delegate
是怎么创建的,这个参数是由 createCoroutineUnintercepted(completion).intercepted()
创建的。
③. 这里有一个判断:
return if (this is BaseContinuationImpl)
create(probeCompletion)
else
createCoroutineFromSuspendFunction(probeCompletion) {
(this as Function1<Continuation<T>, Any?>).invoke(it)
}
首先,this
是什么?this
指代的是我们的 suspend 函数,很明显 this
肯定不是 BaseContinuationImpl,所以就继续往下……
④. 这里也是从一个判断开始的:
val context = completion.context
return if (context === EmptyCoroutineContext){
... ...
}else{
... ...
}
completion
是我们最开始通过 suspend函数 创建协程的那个方法中,自己编写的匿名类。它的 context
就是 EmptyCoroutineContext
,我们直接看 if
上半部分的代码,差异化之类的可以往后再看。
所以这里是创建了一个 RestrictedContinuationImpl 类的对象,我们继续看这个类的实现……
⑤. 这个类可以随便看看……然后继续往下……
⑥. 因为 RestrictedContinuationImpl 好像并没什么重要的实现,所以我们有必要再看看它的父类: BaseContinuationImpl 。
到了这里,最开始的 createCoroutineUnintercepted(completion).intercepted()
已经走完了一半,我们可以知道 createCoroutineUnintercepted(completion)
创建了一个 RestrictedContinuationImpl
的类型。接下来就看另一半的实现……
⑦. 这里其实也是个判断,这里也有个 this
,其指代的就是之前创建好的 RestrictedContinuationImpl 对象,对比一下继承图:很明显,此处的 .intercepted()
还是返回自己,所以可以看作啥事也没有发生。
⑧. 这是 ContinuationImpl 的代码,有兴趣可以看看,该类对于我们现在的研究并没有任何影响……
自此,通过一个 suspend 函数 来创建协程的过程就看完了,对于最后我们创建出了个什么样的玩意儿……我们多少会知道一些,这个(图中SourceCode的代码) coroutine
就是一个 SafeContinuation,但是之前我们提过了,这个类其实是个大老板,真正打工的人是 RestrictedContinuationImpl。
但实际上,为什么会是 RestrictedContinuationImpl 呢?其实也可以是别的通过研究其创建的过程,我们可以知道那是因为我们自己创建的匿名类中,我们需要给其 CoroutineContext 赋一个值,我们默认是赋了 EmptyCoroutineContext
,在创建的过程中会有判断这个值,如果是 EmptyCoroutineContext
那就会创建 RestrictedContinuationImpl ,那如果是别的值,那肯定就是别的类型了。
不过对于研究源码来说,我们前期可以不用关注那么多。
实际上,当我们了解了协程的创建过程,对于就按就其执行的过程也是很有帮助的,赶紧磨磨爪子,开启下一轮的攻势吧……
五、协程的执行过程
协程的执行过程其实是比较复杂的,所以本章并不会聊太多的东西,点到即可……
同样我们继续看图,还是从左上角出发,看到 宝蓝色的数字。
①. 先看看调用的地方: resume()
传入的是一个 Unit,为什么传这个呢?再看到这个方法的实现,可以发现传入值的类型是由协程的泛型来决定的,所以我们回到创建协程的那个函数:
public fun <T> (suspend () -> T).createCoroutine(completion: Continuation<T>)
: Continuation<Unit>=
SafeContinuation(createCoroutineUnintercepted(completion).intercepted(), COROUTINE_SUSPENDED)
返回的协程的泛型就是 Unit 类型,所以我们就可以解释这个操作了……
②. 然后来到 SafeContinuation 的 resumeWith()
函数内,这里有个 cur
,它的赋值为 cur = this.result
,而 this.result = initialResult
,而 initialResult
就是创建该对象传入的第二个参数,所以还是回到创建协程的那个函数(没错,就是上面①中的代码), 我们可以看到传入的参数就是 COROUTINE_SUSPENDED
,所以最后结论就是:cur == COROUTINE_SUSPENDED
。
那么就会执行 delegate.resumeWith(result)
,看到这里,我笑了笑,当老板的生活往往就是这么朴实无华……
③. 所以最后还是 BaseContinuationImpl 来接管了这个执行过程。具体的分析在图中已经给出,需要注意的地方并不多。
④. 我们看到有个关键的代码 invokeSuspend(param)
,这里的 invokeSuspend()
是一个抽象函数,具体的实现在哪里呢?就是该箭头指向的地方,请细品……
⑤⑥. 这两处地方并没有什么值得细说的,仅仅是判断是否有异常。
⑦. 然后就会执行 block(this)
,block()
是一个高阶函数,其实现已经暴露出去,看该箭头指向的地方,然后我们就看到这样一行代码:(this as Function1<Continuation<T>, Any?>).invoke(it)
。
首先,this
是谁?this
还是指代 suspend 函数,然后将他强转成 Function1,这个东西有的讲了(下一篇在讲),然后通过 invoke()
方法,来执行 suspend 函数,这样我们最最最开始的那个 suspend 函数 就被执行了。
suspend 函数 的执行过程已经开了个头,通过以上的研究,我们可以知道为什么通过调用协程的 resume()
方法会唤起执行 suspend 函数。
但是 suspend 函数 的执行过程又是怎样的呢?我们只好下一篇文章再做讨论了……
六、小结
通过这篇文章,其实我们能够对于协程已经有了初步的认识,至少我们掀开了其部分裙角,想要看到更多不妨点个关注一起研究探讨吧。