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


猜你喜欢
- 前言开发项目中需要进行单文件多文件的上传功能,下面演示的ApiResponse是自己分装的返回值,要根据自己的项目来完成。使用的mvvm框架
- 概述在使用Spring Boot的时候我们经常使用actuator,健康检查,bus中使用/refresh等。这里记录如何使用注解的方式自定
- 本文实例为大家分享了Unity Shader实现描边OutLine效果的具体代码,供大家参考,具体内容如下Shader实现描边流程大致为:对
- 一、前言1.1 实现目标服务A调用服务B1和B2(B1和B2提供同种服务),当服务B1/B2在停止和重新发布阶段,或B1/B2有一个服务故障
- 我这里有一个需求需要修改Person类中的一个属性上的注解的值进行修改,例如:public class Person { private i
- 1.系统架构包括哪些形式?C/S架构B/S架构2.什么是C/S架构?说白了就是客户端/服务端,我们需要安装特定的客户端软卷,例如:QQ。C/
- 上一篇:C# Redis学习系列一:Redis的认识、下载、安装、使用一.redis 设置密码使用下载好的 redis-cli.exe指令:
- using System;using System.Collections.Generic;using System.Web.Script.
- 我最近在研究Spring框架的路上,那么今天也算个学习笔记吧!学习一下如何实现Bean的装配方法Bean的简介Java开发者一般会听过Jav
- 一.链表概念链表是一种物理存储结构上非连续存储结构,数据元素的逻辑顺序是通过链表中的引用链接次序实现的 。逻辑结构:注:1、如上图,相当于火
- 进入到Android-sdk中platform-tools目录在命令行中执行以下命令adb shell dumpsys activity&g
- 这周在做公司的一个C#项目中,要写一个webservice提供一个下载方法,之前公司有过,但是要整改,于是这种鸟屎摊子又交给了我,其中一个密
- strftime函数主要用于时间格式化,它的函数原型如下:size_t __cdecl strftime(char * __restrict
- 一、题目描述二、思路语法基础:StringBuilder 类似列表,可以更改元素。package Practice;public class
- 题目:编写一个程序,在面板上移动小球。应该定义一个面板类来显示小球,并提供向上下左右移动小球的方法。请进行边界检查以防止小球移动到视线之外。
- 如有错误,望指正;SpringBoot可以有三种方式定义初始化器,来为容器中增加自定义的对象,具体如下:1、定义在spring.factor
- 如题,有时候看见一个布局写上几百行看上去会非常吃力麻烦,这时候抽取控件样式很有必要了, Android Studio提供了抽取Style样式
- 目录Set接口概述HashSet实现类1、HashSet 具有以下特点:2、HashSet 集合判断两个元素相等的标准3、向HashSet中
- 1、动态SQL片段通过SQL片段达到代码复用 <!-- 动态条件分页查询 --> <sql i
- 1, 泛型接口的协变如果泛型类型用out关键字标注,泛型接口就是协变的。这也意味着返回类型只能是T。泛型接口的抗变如果泛型类型用in关键字标