首发于 Android开发

探索Kotlin协程(三)以同步的方式写异步代码

为了解释清楚后面的代码,先介绍一些Kotlin基础知识。


Kotlin的函数定义

(Int)->Unit 表示这是一个函数类型,其输入参数为Int,输出参数为Unit

fun fetchRemote(onNext:(Int)->Unit){
    Thread.sleep(300)
    val value = 1
    onNext(value)
}

表示函数fetchRemote有一个参数,这个参数名为onNext,它本身就是一个函数;

onNext函数的入参为Int,输出为Unit。

所以,fetchRemote的实际功能就是:

让onNext调用入参1,返回Unit对象。


再进一步

fun fetchLocal(id:Int,onNext:(Int)->Unit) {
    Thread.sleep(300)
    val value = 2
    onNext(id + value)
}

函数fetchLocal的功能是:

将入参id加2,得到的结果作为参数传给onNext指向的函数,然后调用onNext,返回一个Unit对象。


完整代码:以回调函数方式实现耗时操作

    fun fetch(){
        fetchRemote { msg->
            fetchLocal(msg) { result ->
                Log.i("TestCoroutines003", "result:$result")
    fun fetchRemote(onNext:(Int)->Unit){
        Thread.sleep(300)
        val value = 1
        onNext(value)
    fun fetchLocal(id:Int,onNext:(Int)->Unit) {
        Thread.sleep(300)
        val value = 2
        onNext(id + value)
    }

这段代码的功能是:

fetchRemote执行onNext(1),返回结果传给变量msg

fetchLocal将msg作为入参id,执行onNext(id+2),返回结果传给变量result。

最后调用println("result:$result"),将result打印出来。

所以最终的输出结果是:

I/TestCoroutines003: result:3



以一种更加容易理解的方式来阐述这段代码,就是fetchRemote得到结果后,调用回调函数fetchLocal;fetchLocal得到结果后,调用打印函数。

这句话与代码段

fetchRemote { msg->
    fetchLocal(msg) { result ->
        println("result:$result")
}

非常契合。代码几乎就是自然语言一对一的翻译。

以同步的代码实现异步的操作

好了,上面是以回调函数的方式实现功能。下面来看看用suspend怎么做。

    suspend fun fetch():Int{
        val msg = fetchRemote()
        val result = fetchLocal(msg)
        Log.i("TestCoroutines004", "result:$result")
        return result
    suspend fun fetchRemote() = suspendCoroutine<Int> {it ->
        it.resume(1)
    suspend fun fetchLocal(id:Int) = suspendCoroutine<Int> {
        it.resume(id + 2)
    }


在上一节《探索Kotlin协程(二)suspendCoroutine - zhuanlan.zhihu.com/p/56 》中我们已经对suspendCoroutine进行过说明,它的作用就是通过其resume方法,将resume的入参通过Continuation.resumeWith,当作返回值返回。

所以fetchRemote()的返回值是1;

fetchLocal(1)的返回值是3。

因此最后的打印出来的还是:

I/TestCoroutines004: result:3

这个示例生动地说明了Kotlin协程是如何“用同步的代码实现异步的操作”。

编译后的代码

在前面一篇文章《用示例解释Kotlin协程 - zhuanlan.zhihu.com/p/56 》中,我们介绍过Kotlin的suspend挂起关键字,这里我们再重点强调一下:

  • 每个suspend修饰的函数,在编译时都会新增一个Continuation参数,而且其返回值类型变为Any!
  • suspend修饿的函数返回的是不同的状态点,COROUTINE_SUSPENDED就是一个状态点。状态点用label表示。

所以,上面的fetch()函数,编译后的伪代码是这样的:

//加上一个Continuation,返回值变Any
fun fetch(completion: Continuation):Any{
	when(label){
		0 -> {
			//label 为默认值0 ,即fetch函数被第一次调用运行,
			//函数代码此时正常运行,还没接触到任何其他挂起函数
			label = 1
			//下面会运行一个挂起函数,所以状态label立即加1,也就是说label==1,
			//表示代码运行到了第一个挂起函数,此处是fetchRemote()
			val state = fetchRemote()
			return COROUTINE_SUSPENDED
		1 -> {
			//label 1 ,表示在遇到第一个挂起函数fetchRemote() 之后,调用resume等方式恢复了调度
                        label = 2
			//下面会运行下一个挂起函数,所以状态label立即加1,也就是说label==20
			//表示代码运行到了第二个挂起函数,此处是fetchLocal()
			val state = fetchLocal(id)
			return COROUTINE_SUSPENDED
		2 -> {
			//label 2 ,表示在遇到第二个挂起函数fetchLocal() 之后,调用resume等方式恢复了调度