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的资料请关注我们其它相关文章!

(0)

相关推荐

  • Kotlin实现Android系统悬浮窗详解

    目录 Android 弹窗浅谈 系统悬浮窗具体实现 权限申请 代码设计 具体实现 FloatWindowService 类 FloatWindowManager 类 FloatWindowManager 类代码 FloatLayout 类及其 Layout HomeKeyObserverReceiver 类 FloatWindowUtils 类 总结 Android 弹窗浅谈 我们知道 Android 弹窗中,有一类弹窗会在应用之外也显示,这是因为他被申明成了系统弹窗,除此之外还有2类弹窗分别是

  • Kotlin与Java相互调用的完整实例

    目录 一.Kotlin 调用 Java 二.Java 调用 Kotlin 附 Github 源码: 总结 一.Kotlin 调用 Java 1. kotlin 关键字转义 java 中的方法或变量 是 kotlin 的关键字时,使用反引号 `` 对关键字进行转义 // java public class JavaDemo { String is; public String getIs() { return is; } public void setIs(String is) { this.is

  • Kotlin协程Dispatchers原理示例详解

    目录 前置知识 demo startCoroutineCancellable intercepted()函数 DefaultScheduler中找dispatch函数 Runnable传入 Worker线程执行逻辑 小结 前置知识 Kotlin协程不是什么空中阁楼,Kotlin源代码会被编译成class字节码文件,最终会运行到虚拟机中.所以从本质上讲,Kotlin和Java是类似的,都是可以编译产生class的语言,但最终还是会受到虚拟机的限制,它们的代码最终会在虚拟机上的某个线程上被执行. 之

  • Kotlin协程启动createCoroutine及创建startCoroutine原理

    目录 createCoroutine 和 startCoroutine startCoroutine调用 createCoroutineUnintercepted intercepted resume 结语 createCoroutine 和 startCoroutine 协程到底是怎么创建和启动的?本篇文章带你揭晓. 在Continuation.kt文件中,有2个基础API,这里单独提出来说一下,方便后面我们理解launch. public fun <T> (suspend () ->

  • kotlin源码结构层次详解

    目录 协程源码的结构 基础层 中间层 平台层 协程源码的结构 在研究Kotlin源码之前,得先搞懂Kotlin源码结构分布.不然找不到该看哪里的代码.看源码之前当然先得有一个目标,最好是带着这个目标去看源码才比较有针对性,抓主流程,不然可能会陷入浩瀚的源码细节中. 协程源码,按道理可以分成2个仓库,一个是Kotlin仓库,一个是Kotlin协程仓库. Kotlin仓库 https://github.com/JetBrains/kotlin 协程仓库 kotlinx.coroutines http

  • java协程框架quasar和kotlin中的协程对比分析

    目录 前言 快速体验 添加依赖 添加javaagent 线程VS协程 协程代码 多线程代码 协程完胜 后记 前言 早就听说Go语言开发的服务不用任何架构优化,就可以轻松实现百万级别的qps.这得益于Go语言级别的协程的处理效率.协程不同于线程,线程是操作系统级别的资源,创建线程,调度线程,销毁线程都是重量级别的操作.而且线程的资源有限,在java中大量的不加限制的创建线程非常容易将系统搞垮.接下来要分享的这个开源项目,正是解决了在java中只能使用多线程模型开发高并发应用的窘境,使得java也能

  • Kotlin协程launch原理详解

    目录 正文 launch使用 launch原理 CoroutineStart中找invoke方法 startCoroutineCancellable逻辑 小结 正文 launch我们经常用,今天来看看它是什么原理. 建议: 食用本篇文章之前记得先食用Kotlin协程之createCoroutine和startCoroutine launch使用 launch我们应该很熟悉了,随便举个例子: fun main() { val coroutineScope = CoroutineScope(Job(

  • kotlin 协程上下文异常处理详解

    目录 引言 一.协程上下文 1.CoroutineContext 2.CorountineScope 3.子协程继承父协程 二.协程的异常传递 1.协程的异常传播 2.不同上下文(没有继承关系)之间协程异常会怎么样? 3.向用户暴露异常 三.协程的异常处理 使用SupervisorJob 异常捕获器CoroutineExceptionHandler Android中全局异常的处理 引言 从前面我们可以大致了解了协程的玩法,如果一个协程中使用子协程,那么该协程会等待子协程执行结束后才真正退出,而达

  • Kotlin协程launch启动流程原理详解

    目录 1.launch启动流程 反编译后的Java代码 2.协程是如何被启动的 1.launch启动流程 已知协程的启动方式之一是Globalscope.launch,那么Globalscope.launch的流程是怎样的呢,直接进入launch的源码开始看起. fun main() { coroutineTest() Thread.sleep(2000L) } val block = suspend { println("Hello") delay(1000L) println(&q

  • Kotlin全局捕捉协程异常方法详解

    单个异常捕捉 val handler = CoroutineExceptionHandler { coroutineContext, throwable -> Log.d(TAG, "onCreate: handler${throwable}") } Log.d(TAG, "onCreate:1") findViewById<Button>(R.id.button).also { it.setOnClickListener { GlobalScop

  • Python中协程用法代码详解

    本文研究的主要是python中协程的相关问题,具体介绍如下. Num01–>协程的定义 协程,又称微线程,纤程.英文名Coroutine. 首先我们得知道协程是啥?协程其实可以认为是比线程更小的执行单元. 为啥说他是一个执行单元,因为他自带CPU上下文.这样只要在合适的时机, 我们可以把一个协程 切换到另一个协程. 只要这个过程中保存或恢复 CPU上下文那么程序还是可以运行的. Num02–>协程和线程的差异 那么这个过程看起来和线程差不多.其实不然, 线程切换从系统层面远不止保存和恢复 CP

  • Kotlin协程概念原理与使用万字梳理

    目录 一.协程概述 1.概念 2.特点 3.原理 二.协程基础 1.协程的上下文 2.协程的作用域 3.协程调度器 4.协程的启动模式 5.协程的生命周期 三.协程使用 1.协程的启动 2.协程间通信 3.多路复用 4.序列生成器 5.协程异步流 6.全局上下文 一.协程概述 1.概念 协程是Coroutine的中文简称,co表示协同.协作,routine表示程序.协程可以理解为多个互相协作的程序.协程是轻量级的线程,它的轻量体现在启动和切换,协程的启动不需要申请额外的堆栈空间:协程的切换发生在

  • python多任务之协程的使用详解

    1|0使用yield完成多任务 import time def test1(): while True: print("--1--") time.sleep(0.5) yield None def test2(): while True: print("--2--") time.sleep(0.5) yield None if __name__ == "__main__": t1 = test1() t2 = test2() while True

  • golang协程池设计详解

    Why Pool go自从出生就身带"高并发"的标签,其并发编程就是由groutine实现的,因其消耗资源低,性能高效,开发成本低的特性而被广泛应用到各种场景,例如服务端开发中使用的HTTP服务,在golang net/http包中,每一个被监听到的tcp链接都是由一个groutine去完成处理其上下文的,由此使得其拥有极其优秀的并发量吞吐量 for { // 监听tcp rw, e := l.Accept() if e != nil { ....... } tempDelay = 0

  • Python gevent协程切换实现详解

    一.背景 大家都知道gevent的机制是单线程+协程机制,当遇到可能会阻塞的操作时,就切换到可运行的协程中继续运行,以此来实现提交系统运行效率的目标,但是具体是怎么实现的呢?让我们直接从代码中看一下吧. 二.切换机制 让我们从socket的send.recv方法入手: def recv(self, *args): while 1: try: return self._sock.recv(*args) except error as ex: if ex.args[0] != EWOULDBLOCK

随机推荐