Kotlin协程launch原理详解

Kotlin协程launch原理详解

目录

正文

launch使用

launch原理

CoroutineStart中找invoke方法

startCoroutineCancellable逻辑

小结

正文

launch我们经常用,今天来看看它是什么原理。

建议: 食用本篇文章之前记得先食用Kotlin协程之createCoroutine和startCoroutine

launch使用

launch我们应该很熟悉了,随便举个例子:

fun main() { val coroutineScope = CoroutineScope(Job()) coroutineScope.launch { println("1969年 叶文洁进入红岸基地") println("1971年 红岸基地,叶文洁第一次向太阳发送信号,但未发现回波") delay(4000L) println("1975年 半人马座三星,三体世界得知地球存在") } Thread.sleep(5000L) }

sleep(5000L)和launch内部是在2个线程中,互不干涉

简单地使用launch配合delay输出了几条语句。为了了解它底层的实现原理,还是老规矩,先反编译一下。

public final class LaunchTestKt { public static final void main() { Job unused = BuildersKt__Builders_commonKt.launch$default(CoroutineScopeKt.CoroutineScope(JobKt.Job$default((Job) null, 1, (Object) null)), (CoroutineContext) null, (CoroutineStart) null, new LaunchTestKt$main$1((Continuation<? super LaunchTestKt$main$1>) null), 3, (Object) null); Thread.sleep(5000); } } final class LaunchTestKt$main$1 extends SuspendLambda implements Function2<CoroutineScope, Continuation<? super Unit>, Object> { int label; LaunchTestKt$main$1(Continuation<? super LaunchTestKt$main$1> continuation) { super(2, continuation); } public final Continuation<Unit> create(Object obj, Continuation<?> continuation) { return new LaunchTestKt$main$1(continuation); } public final Object invoke(CoroutineScope coroutineScope, Continuation<? super Unit> continuation) { return ((LaunchTestKt$main$1) create(coroutineScope, continuation)).invokeSuspend(Unit.INSTANCE); } public final Object invokeSuspend(Object $result) { Object coroutine_suspended = IntrinsicsKt.getCOROUTINE_SUSPENDED(); switch (this.label) { case 0: ResultKt.throwOnFailure($result); System.out.println("1969年 叶文洁进入红岸基地"); System.out.println("1971年 红岸基地,叶文洁第一次向太阳发送信号,但未发现回波"); this.label = 1; if (DelayKt.delay(4000, this) != coroutine_suspended) { break; } else { return coroutine_suspended; } case 1: ResultKt.throwOnFailure($result); break; default: throw new IllegalStateException("call to 'resume' before 'invoke' with coroutine"); } System.out.println("1975年 半人马座三星,三体世界得知地球存在"); return Unit.INSTANCE; } }

ps:上面这段代码是通过jadx反编译apk的方式拿到的源码,看起来更加人性化。具体的流程是我们用Android Studio写个挂起函数的demo,然后编译成apk,然后将apk用jadx反编译一下,拿到对应class的反编译Java源码,这样弄出来的源码我感觉比直接通过Android Studio的Tools->Kotlin->Show Kotlin拿到的源码稍微好看懂一些。

咦,LaunchTestKt$main$1有没有很眼熟?这不就是前面我们分析startCoroutine原理时得到的匿名内部类么,简直一模一样。这个LaunchTestKt$main$1类对应的是launch的Lambda块,它本质上是一个Continuation。

startCoroutine原理

LaunchTestKt$main$1相关的原理,在前面已经分析过了,这里不再赘述。这里主要看一下launch是如何与这个LaunchTestKt$main$1进行关联的。

launch原理

launch函数如下:

public fun CoroutineScope.launch( context: CoroutineContext = EmptyCoroutineContext, start: CoroutineStart = CoroutineStart.DEFAULT, block: suspend CoroutineScope.() -> Unit ): Job { //代码1 val newContext = newCoroutineContext(context) //代码2 val coroutine = if (start.isLazy) LazyStandaloneCoroutine(newContext, block) else StandaloneCoroutine(newContext, active = true) //代码3 coroutine.start(start, coroutine, block) return coroutine }

将传入的CoroutineContext构造出新的context

启动模式,判断是否为懒加载,如果是懒加载则构建懒加载协程对象,否则就是标准的

启动协程

context相关的先不看,因为我们demo这里不是懒加载的所以创建出来的是StandaloneCoroutine,直接看一下start是怎么启动协程的。

private open class StandaloneCoroutine( parentContext: CoroutineContext, active: Boolean ) : AbstractCoroutine<Unit>(parentContext, initParentJob = true, active = active) { override fun handleJobException(exception: Throwable): Boolean { handleCoroutineException(context, exception) return true } } public abstract class AbstractCoroutine<in T>( parentContext: CoroutineContext, initParentJob: Boolean, active: Boolean ) : JobSupport(active), Job, Continuation<T>, CoroutineScope { public fun <R> start(start: CoroutineStart, receiver: R, block: suspend R.() -> T) { start(block, receiver, this) } }

start函数是在父类AbstractCoroutine中实现的,这个start函数里面又调用了一个新的start函数,当我们点击这个里面的start函数想进去看源码时发现,点不过去,点了之后还是在当前位置..... ???啥情况

CoroutineStart中找invoke方法

仔细观察发现,start是一个CoroutineStart对象,直接使用CoroutineStart对象然后后面就接括号了,这是类里面有定义operator invoke方法,然后Kotlin可以通过这种方式来简化调用。我们直接去CoroutineStart中找invoke方法:

public enum class CoroutineStart { DEFAULT, LAZY, ATOMIC, UNDISPATCHED; public operator fun <R, T> invoke(block: suspend R.() -> T, receiver: R, completion: Continuation<T>): Unit = when (this) { DEFAULT -> block.startCoroutineCancellable(receiver, completion) ATOMIC -> block.startCoroutine(receiver, completion) UNDISPATCHED -> block.startCoroutineUndispatched(receiver, completion) LAZY -> Unit // will start lazily } public val isLazy: Boolean get() = this === LAZY }

CoroutineStart是个枚举类,定义了协程的几种启动方式:DEFAULT、LAZY、ATOMIC、UNDISPATCHED。在invoke函数中,根据当前是哪种启动方式进行开启协程。

当如果使用ATOMIC的方式,也就是不可取消的协程,就触发了block.startCoroutine(receiver, completion)。有没有觉得很眼熟,它其实就是我们上节课中分析的启动协程的关键:startCoroutine。

demo中使用的是默认的方式,也就是DEFAULT,它只不过是在ATOMIC的基础上,对startCoroutine包装了一下,使其成为可响应取消的协程。而UNDISPATCHED的方式,也就是不分发到其他线程去执行,而是直接在当前线程中进行执行。

startCoroutineCancellable逻辑

来看下DEFAULT之后走的startCoroutineCancellable逻辑:

public fun <T> (suspend () -> T).startCoroutineCancellable(completion: Continuation<T>): Unit = runSafely(completion) { createCoroutineUnintercepted(completion).intercepted().resumeCancellableWith(Result.success(Unit)) } public actual fun <T> (suspend () -> T).createCoroutineUnintercepted( completion: Continuation<T> ): Continuation<Unit> { val probeCompletion = probeCoroutineCreated(completion) return if (this is BaseContinuationImpl) //走这里 create(probeCompletion) else createCoroutineFromSuspendFunction(probeCompletion) { (this as Function1<Continuation<T>, Any?>).invoke(it) } }

这块就是前面文章中分析的代码了,launch就算是走完了。其本质上是对startCoroutine()这个基础API进行了一些封装,让开发者更方便使用。

小结

launch、async之类的是Kotlin协程框架中的中间层,它们是协程构建器。而在协程构建器的内部,实际上是对协程基础API: createCoroutine{}startCoroutine{}的封装。它们除了拥有启动协程的基础能力,还支持传入CoroutineContext(结构化并发)、CoroutineStart(启动模式) 等参数,方便开发者使用

以上就是Kotlin协程launch原理详解的详细内容,更多关于Kotlin协程launch的资料请关注易知道(ezd.cc)其它相关文章!

推荐阅读

    1、Kotlin - 1.3;

    1、Kotlin - 1.3;,编译器,函数,协程  经过长时间的测试,协程发布了稳定版本, 这意味着从Kotlin 1.3开始,语言支持和API完全稳定。 协程概

    Java中的Kotlin 内部类原理

    Java中的Kotlin 内部类原理目录Java 中的内部类OutterJava.classInnJava.classKotlin 中的内部类总结Java 中的内部类
    这是一个 Java

    Kotlin函数使用示例教程

    Kotlin函数使用示例教程目录我们先看看简单的函数我们写一个求和函数函数头函数体调用函数非常简单反编译Java源代码步骤接下来我们来看

    Java结合Kotlin实现宝宝年龄计算

    Java结合Kotlin实现宝宝年龄计算目录通过生日计算宝宝多大了输出结果再来一个Kotlin版输出结果通过生日计算宝宝多大了
    前段时间大家在

    详解如何实现一个Kotlin函数类型

    详解如何实现一个Kotlin函数类型目录接口与函数类型总结接口与函数类型
    业务开发中,经常会有实现一个函数式接口(即接口只有一个方法需要

    Kotlin对象比较注意点示例详解

    Kotlin对象比较注意点示例详解目录背景原因另一个问题解决办法结论背景
    现有一个StateFlow及其监听
    private val stateFlow = MutableSt

    Kotlin的::符号怎么用

    Kotlin的::符号怎么用目录前言正文传递函数优化成员引用 ::使用范围绑定引用总结前言
    在阅读Kotlin的代码时,经常有看到 :: 这个符号,这个

    Kotlin原理详析之拓展函数

    Kotlin原理详析之拓展函数目录原理限制不能访问私有成员拓展函数不能实现多态成员函数优先级高,拓展函数不能实现重写为什么要使用Kotli

    Kotlin中的contract到底有什么用详解

    Kotlin中的contract到底有什么用详解目录前言测试总结前言
    我们在开发中肯定会经常用Kotlin提供的一些通用拓展函数,当我们进去看源码的