Kotlin协程launch原理详解

作者:xfhy 时间:2023-05-19 09:36:01 

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,
    * IC,
   UNDISPATCHED;
   public operator fun <R, T> invoke(block: suspend R.() -> T, receiver: R, completion: Continuation<T>): Unit =
       when (this) {
           DEFAULT -> block.startCoroutineCancellable(receiver, completion)
            * IC -> block.startCoroutine(receiver, completion)
           UNDISPATCHED -> block.startCoroutineUndispatched(receiver, completion)
           LAZY -> Unit // will start lazily
       }
   public val isLazy: Boolean get() = this === LAZY
}

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

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

demo中使用的是默认的方式,也就是DEFAULT,它只不过是在 * IC的基础上,对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(启动模式) 等参数,方便开发者使用

来源:https://github.com/xfhy/Android-Notes/blob/master/Blogs/Kotlin/4.Kotlin

标签:Kotlin,协程,launch
0
投稿

猜你喜欢

  • Diycode开源项目实例搭建上拉加载和下拉刷新的Fragment

    2023-06-17 01:13:03
  • Java Email邮件发送简单实现介绍

    2023-10-07 01:05:11
  • 通过Session案例分析一次性验证码登录

    2023-04-27 06:25:51
  • C#多维数组学习使用

    2023-07-06 11:51:02
  • javaweb中Http协议详解

    2022-03-21 05:12:41
  • C#开发中的垃圾回收机制简析

    2022-08-06 14:10:56
  • C++ 实现求最大公约数和最小公倍数

    2023-10-30 10:49:23
  • C# AttributeUsage使用案例详解

    2022-06-25 08:12:09
  • C#Js时间格式化问题简单实例

    2023-05-17 01:49:19
  • 深入了解JVM字节码增强技术

    2021-06-04 16:28:13
  • Unity实现聊天室功能

    2023-12-20 19:53:34
  • C#程序中创建、复制、移动、删除文件或文件夹的示例

    2022-04-28 11:26:06
  • c# 图片加密解密的实例代码

    2023-08-20 21:21:01
  • 详解Android Studio安装ButterKnife插件(手动安装)

    2023-07-06 13:59:32
  • 新手Hadoop安装 环境搭建

    2022-12-15 05:34:02
  • 详解c# 线程同步

    2023-11-23 08:54:48
  • Java 中的HashMap详解和使用示例_动力节点Java学院整理

    2022-06-10 18:08:51
  • 手把手教你SpringBoot过滤器N种注册方式

    2023-08-08 08:28:45
  • java多线程-同步块实例讲解

    2022-06-21 02:10:41
  • 解析Java的JNI编程中的对象引用与内存泄漏问题

    2023-03-19 20:59:28
  • asp之家 软件编程 m.aspxhome.com