北京建设电工证查询网站,平面设计网课培训有用吗,wordpress html cdn,网站开发连接效果今天我们来聊聊Kotlin的协程Coroutine。如果你还没有接触过协程#xff0c;推荐你先阅读这篇入门级文章What? 你还不知道Kotlin Coroutine?如果你已经接触过协程#xff0c;但对协程的原理存在疑惑#xff0c;那么在阅读本篇文章之前推荐你先阅读下面的文章#xff0c;这…今天我们来聊聊Kotlin的协程Coroutine。如果你还没有接触过协程推荐你先阅读这篇入门级文章What? 你还不知道Kotlin Coroutine?如果你已经接触过协程但对协程的原理存在疑惑那么在阅读本篇文章之前推荐你先阅读下面的文章这样能让你更全面更顺畅的理解这篇文章。Kotlin协程实现原理:SuspendCoroutineContextKotlin协程实现原理:CoroutineScopeJobKotlin协程实现原理:ContinuationInterceptorCoroutineDispatcher如果你已经接触过协程相信你都有过以下几个疑问协程到底是个什么东西协程的suspend有什么作用工作原理是怎样的协程中的一些关键名称(例如Job、Coroutine、Dispatcher、CoroutineContext与CoroutineScope)它们之间到底是怎么样的关系协程的所谓非阻塞式挂起与恢复又是什么协程的内部实现原理是怎么样的...接下来的一些文章试着来分析一下这些疑问也欢迎大家一起加入来讨论。挂起协程是使用非阻塞式挂起的方式来保证协程运行的。那么什么是非阻塞式挂起呢下面我们来聊聊挂起到底是一个怎样的操作。在之前的文章中提及到suspend关键字它的一个作用是代码调用的时候会为方法添加一个Continuation类型的参数保证协程中Continuaton的上下传递。而它另一个关键作用是起到挂起协程的标识。协程运行的时候每遇到被suspend修饰的方法时都有可能会挂起当前的协程。注意是有可能。你可以随便写一个方法该方法也可以被suspend修饰但这种方法在协程中调用是不会被挂起的。例如private suspend fun a() {println(aa)
}lifecycleScope.launch {a()
}
因为这种方法是不会返回COROUTINE_SUSPENDED类型的。协程被挂起的标志是对应的状态下返回COROUTINE_SUSPENDED标识。更深入一点的话就涉及到状态机。协程内部是使用状态机来管理协程的各个挂起点。文字有点抽象具体我们还是来看代码。我们就拿上面的a方法例子来说明。首先在Android Studio打开这段代码的Kotlin Bytecode。可以在Tools - Kotlin - Show Kotlin Bytecode中打开。然后点击其中的Decompile选项生成对应的反编译java代码。最终代码如下BuildersKt.launch$default((CoroutineScope)LifecycleOwnerKt.getLifecycleScope(this), (CoroutineContext)null, (CoroutineStart)null, (Function2)(new Function2((Continuation)null) {private CoroutineScope p$;Object L$0;int label;Nullablepublic final Object invokeSuspend(NotNull Object $result) {// 挂起标识Object var3 IntrinsicsKt.getCOROUTINE_SUSPENDED();CoroutineScope $this$launch;switch(this.label) {case 0:ResultKt.throwOnFailure($result);$this$launch this.p$;MainActivity var10000 MainActivity.this;// 保存现场this.L$0 $this$launch;// 设置挂起后恢复时进入的状态this.label 1;// 判断是否挂起if (var10000.a(this) var3) {// 挂起跳出该方法return var3;}// 不需要挂起协程继续执行其他逻辑break;case 1:// 恢复现场$this$launch (CoroutineScope)this.L$0;// 是否需要抛出异常ResultKt.throwOnFailure($result);break;default:throw new IllegalStateException(call to resume before invoke with coroutine);}return Unit.INSTANCE;}NotNullpublic final Continuation create(Nullable Object value, NotNull Continuation completion) {Intrinsics.checkParameterIsNotNull(completion, completion);Function2 var3 new anonymous constructor(completion);var3.p$ (CoroutineScope)value;return var3;}public final Object invoke(Object var1, Object var2) {return ((undefinedtype)this.create(var1, (Continuation)var2)).invokeSuspend(Unit.INSTANCE);}
}), 3, (Object)null);
上面的代码就是协程的状态机通过label来代表不同的状态从而对应执行不同case中的逻辑代码。在之前的文章中已经介绍过协程启动的时候会手动调用一次resumeWith方法而它对应的内部逻辑就是执行上面的invokeSuspend方法。所以首次运行协程时label值为0进入case 0:语句。此时会记录现场为可能被挂起的状态做准备并设置下一个可能被执行的状态。如果a方法的返回值为var3这个var3对应的就是COROUTINE_SUSPENDED。所以只有当a方法返回COROUTINE_SUSPENDED时才会执行if内部语句跳出方法此时协程就被挂起。当前线程也就可以执行其它的逻辑并不会被协程的挂起所阻塞。所以协程的挂起在代码层面来说就是跳出协程执行的方法体或者说跳出协程当前状态机下的对应状态然后等待下一个状态来临时在进行执行。那为什么说我们写的这个a方法不会被挂起呢Nullable
final Object a(NotNull Continuation $completion) {return Unit.INSTANCE;
}
原来是它的返回值并不是COROUTINE_SUSPENDED。既然它不会被挂起那么什么情况下的方法才会被挂起呢很简单如果我们在a方法中加入delay方法它就会被挂起。Nullable
final Object a(NotNull Continuation $completion) {Object var10000 DelayKt.delay(1000L, $completion);return var10000 IntrinsicsKt.getCOROUTINE_SUSPENDED() ? var10000 : Unit.INSTANCE;
}
真正触发挂起的是delay方法因为delay方法会创建自己Continuation同时内部调用getResult方法。 internal fun getResult(): Any? {installParentCancellationHandler()if (trySuspend()) return COROUTINE_SUSPENDED// otherwise, onCompletionInternal was already invoked invoked tryResume, and the result is in the stateval state this.stateif (state is CompletedExceptionally) throw recoverStackTrace(state.cause, this)return getSuccessfulResult(state)}
在getResult方法中会通过trySuspend来判断挂起当前协程。由挂起自身的协程从而触发挂起父类的协程。如果只是为了测试可以让a方法直接返回COROUTINE_SUSPENDED private suspend fun a(): Any {return kotlin.coroutines.intrinsics.COROUTINE_SUSPENDED}
当然线上千万不能这样写因为一旦这样写协程将一直被挂起因为你没有将其恢复的能力。恢复现在我们再来聊一聊协程的恢复。协程的恢复本质是通过Continuation的resumeWith方法来触发的。下面我们来看一个可以挂起的例子通过它来分析协程挂起与恢复的整个流程。println(main start)
lifecycleScope.launch {println(async start)val b async {delay(2000)async}b.await()println(async end)
}
Handler().postDelayed({println(main end)
}, 1000)
Kotlin代码很简单当前协程运行与主线程中内部执行一个async方法通过await方法触发协程的挂起。再来看它的对应反编译java代码// 1
String var2 main start;
System.out.println(var2);
BuildersKt.launch$default((CoroutineScope)LifecycleOwnerKt.getLifecycleScope(this), (CoroutineContext)null, (CoroutineStart)null, (Function2)(new Function2((Continuation)null) {private CoroutineScope p$;Object L$0;Object L$1;int label;Nullablepublic final Object invokeSuspend(NotNull Object $result) {Object var5 IntrinsicsKt.getCOROUTINE_SUSPENDED();CoroutineScope $this$launch;Deferred b;switch(this.label) {case 0:// 2ResultKt.throwOnFailure($result);$this$launch this.p$;String var6 async start;System.out.println(var6);b BuildersKt.async$default($this$launch, (CoroutineContext)null, (CoroutineStart)null, (Function2)(new Function2((Continuation)null) {private CoroutineScope p$;Object L$0;int label;Nullablepublic final Object invokeSuspend(NotNull Object $result) {Object var3 IntrinsicsKt.getCOROUTINE_SUSPENDED();CoroutineScope $this$async;switch(this.label) {case 0:// 3ResultKt.throwOnFailure($result);$this$async this.p$;this.L$0 $this$async;this.label 1;if (DelayKt.delay(2000L, this) var3) {return var3;}break;case 1:// 5、6$this$async (CoroutineScope)this.L$0;ResultKt.throwOnFailure($result);break;default:throw new IllegalStateException(call to resume before invoke with coroutine);}return async;}NotNullpublic final Continuation create(Nullable Object value, NotNull Continuation completion) {Intrinsics.checkParameterIsNotNull(completion, completion);Function2 var3 new anonymous constructor(completion);var3.p$ (CoroutineScope)value;return var3;}public final Object invoke(Object var1, Object var2) {return ((undefinedtype)this.create(var1, (Continuation)var2)).invokeSuspend(Unit.INSTANCE);}}), 3, (Object)null);this.L$0 $this$launch;this.L$1 b;this.label 1;if (b.await(this) var5) {return var5;}break;case 1:// 7b (Deferred)this.L$1;$this$launch (CoroutineScope)this.L$0;ResultKt.throwOnFailure($result);break;default:throw new IllegalStateException(call to resume before invoke with coroutine);}// 8String var4 async end;System.out.println(var4);return Unit.INSTANCE;}NotNullpublic final Continuation create(Nullable Object value, NotNull Continuation completion) {Intrinsics.checkParameterIsNotNull(completion, completion);Function2 var3 new anonymous constructor(completion);var3.p$ (CoroutineScope)value;return var3;}public final Object invoke(Object var1, Object var2) {return ((undefinedtype)this.create(var1, (Continuation)var2)).invokeSuspend(Unit.INSTANCE);}
}), 3, (Object)null);
// 4
(new Handler()).postDelayed((Runnable)null.INSTANCE, 1000L);
有点长没关系我们只看关键点看它的状态机相关的内容。首先会输出main start然后通过launch创建协程进入协程状态机此时label为0执行case: 0相关逻辑。进入case: 0后输出async start调用async并通过await来挂起当前协程再挂起的过程中记录当前挂起点的数据并将lable设置为1。进入async创建的协程此时async协程中的lable为0进入async case: 0执行dealy并挂起async的协程。并将label设置为1。等待2s之后被唤醒。此时协程都被挂起即跳出协程launch方法执行handler操作。由于post 1s所以比协程中dealy还短所以会优先输出main end然后再过1s进入恢复协程阶段async中的协程被delay恢复注意在delay方法中传入了thisasync的Continuation对象所以delay内部一旦完成2s计时就会调用Continuation的resumeWith方法来恢复async中的协程即调用invokeSuspend方法。由于被挂起之前已经将async label设置为1所以进入case: 1恢复之前挂起的现场检查异常最终返回async。此时await挂起点被恢复注意它也传入了this对应的就是launch中的Continuation所以也会回调resumeWith方法最终调用invokeSuspend即进入case 1:恢复现场结束状态机。最后再继续输出async end协程运行结束。我们可以执行上面的代码来验证输出是否正确main start
async start
main end
async end
我们来总结一下协程通过suspend来标识挂起点但真正的挂起点还需要通过是否返回COROUTINE_SUSPENDED来判断而代码体现是通过状态机来处理协程的挂起与恢复。在需要挂起的时候先保留现场与设置下一个状态点然后再通过退出方法的方式来挂起协程。在挂起的过程中并不会阻塞当前的线程。对应的恢复通过resumeWith来进入状态机的下一个状态同时在进入下一个状态时会恢复之前挂起的现场。本篇文章主要介绍了协程的挂起与恢复原理同时也分析了协程的状态机相关的执行过程。希望对学习协程的伙伴们能够有所帮助敬请期待后续的协程分析。项目android_startup: 提供一种在应用启动时能够更加简单、高效的方式来初始化组件优化启动速度。不仅支持Jetpack App Startup的全部功能还提供额外的同步与异步等待、线程控制与多进程支持等功能。AwesomeGithub: 基于Github客户端纯练习项目支持组件化开发支持账户密码与认证登陆。使用Kotlin语言进行开发项目架构是基于JetpackDataBinding的MVVM项目中使用了Arouter、Retrofit、Coroutine、Glide、Dagger与Hilt等流行开源技术。flutter_github: 基于Flutter的跨平台版本Github客户端与AwesomeGithub相对应。android-api-analysis: 结合详细的Demo来全面解析Android相关的知识点, 帮助读者能够更快的掌握与理解所阐述的要点。daily_algorithm: 每日一算法由浅入深欢迎加入一起共勉。