Kotlin协程分析(一)——协程的创建过程和执行过程

一、前言

协程不是进程或线程,其执行过程更类似于子例程(函数属其一种)
——百度百科

协程的概念很早就有了,一些相关的历史我就不必赘言,直接进入主题吧。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 函数就需要借助 KotlinAPI

扫描二维码关注公众号,回复: 11977981 查看本文章
//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 类,对于协程的创建过程,我们不需要仔细的阅读其代码。直接看到它的第一个构造参数,是一个名为 delegateContinuation 类型,看到这里我们基本就可以猜测: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 类型,所以我们就可以解释这个操作了……

②. 然后来到 SafeContinuationresumeWith() 函数内,这里有个 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 函数 的执行过程又是怎样的呢?我们只好下一篇文章再做讨论了……

六、小结

通过这篇文章,其实我们能够对于协程已经有了初步的认识,至少我们掀开了其部分裙角,想要看到更多不妨点个关注一起研究探讨吧。

在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/catzifeng/article/details/108304338