上文链接:Kotlin协程源码分析-4 状态机

在上文中最后一行 “挂起函数的Continuation对象如何才能获取?”。“如何返回一个挂起标识符COROUTINE_SUSPENDED”。本文将做就这两个问题详细介绍

获取挂起函数的Continuation对象

你可以直接看kotlin协程库实现方式得到答案。这里直接给出答案。


suspend fun mySuspendOne() = suspendCoroutineUninterceptedOrReturn<String> { continuation ->
	//传入的continuation就是SuspendLambda对象,也就是我们自己写一个lambda编译后的对象
    //启动一个线程。一秒后回调continuation函数
    thread {
        TimeUnit.SECONDS.sleep(1)
        logD("我将调用Continuation返回一个数据")
        //调用又会重新调用BaseContinuationImpl的resumeWith函数
        continuation.resume("hello world")
    }

    logD("mySuspendOne 函数返回")
    //因为我们这个函数没有返回正确结果,所以必须返回一个挂起标识,否则BaseContinuationImpl会认为完成了任务。 
    // 并且我们的线程又在运行没有取消,这将很多意想不到的结果
    kotlin.coroutines.intrinsics.COROUTINE_SUSPENDED
}

上面的代码会在编译后变成如下:
在这里插入图片描述

上面其实很危险,如果我们函数忘记写返回COROUTINE_SUSPENDED那么默认返回UnitBaseContinuationImplresumeWith判断不是挂起标识,那么会认为完成了任务,此时会回调监听的完成的Continuation对象.这自然会引起很多意外的现象.
Kotlin考虑到这种情况下推荐我们使用如下的方法获取当前continuation

suspend fun mySafeSuspendOne() = suspendCoroutine<String> { continuation ->

    //传入的continuation就是SuspendLambda对象,也就是我们自己写一个lambda编译后的对象
    //启动一个线程。一秒后回调continuation函数
    thread {
        TimeUnit.SECONDS.sleep(1)
        logD("我将调用Continuation返回一个数据")
        //调用又会重新调用BaseContinuationImpl的resumeWith函数
        continuation.resume("hello world")
    }

    logD("mySuspendOne 函数返回")
    //suspendCoroutine函数很聪明的帮我们判断返回结果如果不是想要的对象,自动返回kotlin.coroutines.intrinsics.COROUTINE_SUSPENDED
}

我们简单看下suspendCoroutine实现

public suspend inline fun <T> suspendCoroutine(crossinline block: (Continuation<T>) -> Unit): T =
    suspendCoroutineUninterceptedOrReturn { c: Continuation<T> ->
    	//封装一个代理Continuation对象
        val safe = SafeContinuation(c.intercepted())
        block(safe)
        //根据block返回结果判断要不要返回COROUTINE_SUSPENDED
        safe.getOrThrow()
    }

这里先跳过SafeContinuation代理对象的分析。 简单理解就是帮你判断是不是要返回COROUTINE_SUSPENDED(当然这个SafeContinuation还有栈相关的内容)。

我们来一个案例

//com.example.studycoroutine.chapter.five.FiveDemo.kt

suspend fun mySuspendOne() = suspendCoroutineUninterceptedOrReturn<String> { continuation ->

    //传入的continuation就是SuspendLambda对象,也就是我们自己写一个lambda编译后的对象
    //启动一个线程。一秒后回调continuation函数
    thread {
        TimeUnit.SECONDS.sleep(1)
        logD("我将调用Continuation返回一个数据")
        //调用又会重新调用BaseContinuationImpl的resumeWith函数
        continuation.resume("hello world")
    }

    logD("mySuspendOne 函数返回")
    //因为我们这个函数没有返回正确结果,所以必须返回一个挂起标识,否则BaseContinuationImpl会认为完成了任务。
    // 并且我们的线程又在运行没有取消,这将很多意想不到的结果
    kotlin.coroutines.intrinsics.COROUTINE_SUSPENDED
}

suspend fun mySafeSuspendOne() = suspendCoroutine<String> { continuation ->

    //传入的continuation就是SuspendLambda对象,也就是我们自己写一个lambda编译后的对象
    //启动一个线程。一秒后回调continuation函数
    thread {
        TimeUnit.SECONDS.sleep(1)
        logD("我将调用Continuation返回一个数据")
        //调用又会重新调用BaseContinuationImpl的resumeWith函数
        continuation.resume("hello world ")
    }

    logD("mySuspendOne 函数返回")
    //suspendCoroutine函数很聪明的帮我们判断返回结果如果不是想要的对象,自动返回kotlin.coroutines.intrinsics.COROUTINE_SUSPENDED

}

fun testFiveOne() {


    val myCoroutine = MyCoroutine()
    val mystartSuspend: suspend () -> String = {
        mySuspendOne()
        mySafeSuspendOne()
    }

    //启动协程
    mystartSuspend.startCoroutine(myCoroutine)

    logD("启动协程,由于两个挂起函数延迟返回结果所以这句话会先打出来")
}

输出:

 [main] mySuspendOne 函数返回
 [main] 启动协程,由于两个挂起函数所以这句话会先打出来
 [Thread-2] 我将调用Continuation返回一个数据
 [Thread-2] mySuspendOne 函数返回
 [Thread-3] 我将调用Continuation返回一个数据
 [Thread-3] MyCoroutine 回调resumeWith 返回的结果 hello world 

对于上面的输出顺序读者必须弄清,弄清的时候等于弄清Kotlin协程核心编程思想。

那么这边手动梳理下流程:
首先看下编译后mystartSuspend对象:

static final class MystartSuspend extends SuspendLambda implements Function1 {
    int label;
    Object result;
  
    public final Object invokeSuspend(Object $result) {
        Object SUSPEND_FLAG = IntrinsicsKt.getCOROUTINE_SUSPENDED();
        int current_statue= this.label;
        if(current_statue== 0) {
            ResultKt.throwOnFailure($result);
            this.label = 1;
            //(一)此处返回SUSPEND_FLAG,等mySuspendOne调用传入Continuation对象
            //重新调用到invokeSuspend函数
            if(FiveDemoKt.mySuspendOne(((Continuation)this)) == SUSPEND_FLAG) {
                //(二) 相等所以返回挂起标识给父类结束本次调用。
                return SUSPEND_FLAG;
            }

        label_21:
            this.label = 2;
            //(三) mySuspendOne调用传入Continuation的resume函数回调这
            result = FiveDemoKt.mySafeSuspendOne(((Continuation)this));
            //(四) 此处同样返回挂起标识(一个挂起函数没有立即返回结果那么必然要返回SUSPEND_FLAG)
            if(result == SUSPEND_FLAG) {
            //(五)
                return SUSPEND_FLAG;
            }
        }
        else {
            if(current_statue!= 1) {
                if(current_statue== 2) {
                //(六) 最后返回结果,mySafeSuspendOne回调Continuation的resume的回到这
                    ResultKt.throwOnFailure($result);
                    return $result;
                }

                throw new IllegalStateException("call to \'resume\' before \'invoke\' with coroutine");
            }

            ResultKt.throwOnFailure($result);
            goto label_21;
        }
        return result;
    }
}

上图吧:
在这里插入图片描述

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

那么看完上面的图示,我相信你应该明白所谓kotlin状态机就是接口回调罢了。明白状态机后面在学习kotlin协程就像玩一样。


我们看到MyCoroutine有一个context对象这个是干嘛的?有什么用

下回分析kotlin上下文

Logo

开源鸿蒙跨平台开发社区汇聚开发者与厂商,共建“一次开发,多端部署”的开源生态,致力于降低跨端开发门槛,推动万物智联创新。

更多推荐