内容简介:欢迎回来!距离上一篇博客已经接近一年了,在这期间虽然也有一些奇怪的研究,但是都还没有到达能够写博客的地步。每天都在为琐事和业务代码忙忙碌碌,也没有闲工夫去研究比较有意思的东西。这也算是我给懒癌找的借口,不过还是希望这个系列能够稍微长一点,能够被称为「系列」。众所周知,无论是怎样的协程,最后都逃不出什么 Dispatcher,Woker,EventLoop 之类的东西。所以当遇见了异步操作的时候,最后总是将当前的线程的处理能力交还给调度器,以实现非阻塞式的调用,然后将任务完成的委托注册下来,以便异步任务完成
欢迎回来!
距离上一篇博客已经接近一年了,在这期间虽然也有一些奇怪的研究,但是都还没有到达能够写博客的地步。每天都在为琐事和业务代码忙忙碌碌,也没有闲工夫去研究比较有意思的东西。这也算是我给懒癌找的借口,不过还是希望这个系列能够稍微长一点,能够被称为「系列」。
关于协程
众所周知,无论是怎样的协程,最后都逃不出什么 Dispatcher,Woker,EventLoop 之类的东西。所以当遇见了异步操作的时候,最后总是将当前的线程的处理能力交还给调度器,以实现非阻塞式的调用,然后将任务完成的委托注册下来,以便异步任务完成时调用,实现同步化的异步调用。
简单来说,比如你每天早上的早餐是一块吐司,一杯咖啡,一个煎蛋。那么每天早上你就有三个任务,烤吐司,冲咖啡,煎鸡蛋。
假设你是阻塞式单线程的,你大概就会这样
7:00 烧热水 >>> 5min >>> 7:06 冲咖啡
7:07 将吐司放入面包机 >>> 1min >>> 7:09 吐司制作完成
7:10 煎鸡蛋 >>> 2min >>> 7:13 完成早餐
这样的话,你会浪费 8 分钟的时间在等待烧水、烤面包以及煎蛋,这无疑是最差的做法,这个时候你就会想,假设你具有分身术事情是不是会变得不一样。
分身 A: 7:00 烧热水 >>> 5min >>> 7:06 冲咖啡
分身 B: 7:00 将吐司放入面包机 >>> 1min >>> 7:02 吐司制作完成
分身 C: 7:00 煎鸡蛋 >>> 2min >>> 7:03 完成煎蛋
显而易见,你只需要 6 分钟就能结束所有操作了。然而事实上是你并不会分身术,而且在程序世界中,线程也是比较宝贵的资源。并且再仔细看看,发现大多数的时候你的分身也是在等待中度过,太浪费了。这个时候如果你愿意花费一些脑子,来调度你的早餐任务,就像协程做的这样,统筹一下,就会的得到这样的结果。
7:00 烧热水 >>> 7:01 将吐司放入面包机 >>> 7:02 煎鸡蛋
7:03 吐司制作完成 >>> 继续煎蛋 1min >>> 7:05 完成煎蛋
继续烧水 1min >>> 7:06 冲咖啡完成早餐
也许对你来说这是一个忙碌的早上,但是你节约了 7 分钟的时间。可以看到,使用协程的效果和具有分身术是一样的效率,这就是协程的威力。
但是在程序的世界,对于 CPU 非密集型操作,并且比较耗时的场景下,比如磁盘 IO,网络 IO,协程往往比多线程效率更高,因为少了线程这么重型的操作,并且省去了程序间切换上下文的消耗。这也是最近流行的 NIO 理由,大家都想物尽其用。至于 CPU 密集型操作,还是交给分身术(多线程)比较好。
至于协程和异步的关系,我的理解是,协程是成规模,有规划的异步操作。异步是协程的核心,多个异步操作之间的协调与调度就是协程。
有意思的错误猜测
了解了协程的概念之后,讲一个有意思的事情。在前不久,和同事讨论 Kotlin 是如何实现其协程时,他给出了一个非常有意思的猜测。
由于考虑到 JVM 的呆萌,而且 Kotlin 的协程是以三方库的形式支持的,并没有编译时的支持。基于这些理由,同事给出他的想法。
协程遇到异步操作,需要转交当前线程的控制权给调度器的 EventLoop,那么 Kotlin 是在何时转交这个控制权的呢?
package io.kanro import kotlinx.coroutines.async import kotlinx.coroutines.delay import kotlinx.coroutines.runBlocking fun main(args: Array<String>) = runBlocking { async { delay(1000) // Do some work println("Done once!") }.await() async { delay(1000) // Do some work println("Done twice!") }.await() }
如果在 await 和其他的异步相关方法中,获取当前线程的调度器,并且在里面跑 EventLoop,就可以完成任务调度了,这样也不需要编译时的支持,缺点可能是异常的堆栈追踪可能不太好读,并且整个调用栈会稍微深一些,但是应该还不至于溢出。
如上所示,因为在编译器不干预的前提下,同事给出的答案是在 await() 方法中,这个答案让我耳目一新,之前在我的了解中,C# 对于 async 关键字的处理是会将代码分段拆分,只是由于 async/await 的语法糖,让你觉得他们是在一起的。
C# await 一个 Task 时,会立即返回,并注册完成委托到同步上下文中,以便任务完成时返回这里继续执行,但是由于一个函数的代码域中,不可能执行到一半然后撒手不管了,所以 C# 给出的解决方案是立即返回,并交出线程的使用权,由调度器跑 EventLoop。
Kotlin 也不可能在一个函数执行到一半就切换干别的去了,而且也没有编译器的帮助,不能中途返回。那么就只能由 await 方法调用 EventLoop 了,当任务完成时,跳出这个 Loop,然后继续向下走。调度器和 EventLoop 并不拥有整个过程调度的主导权,全靠用户代码自动调度。
思路和 C# 完全相反,但十分合理的推断,也给了我一个全新的启示。但是遗憾的是,这个猜测是 错误 的。具体原因,我们在下面再探讨。
C# 中的异步
CLR 上的 Task + async/await 可以说是将 async/await 式异步编程带入了一个新的高度,在 ES6 中的 Promise + async/await 也是将 CLR 的异步精神发扬光大。从此再也不需要为了写异步代码绞尽脑汁,写异步和写同步一样的轻松自如。
要说 C# 的 async/await 的原理,要从老式的 C# 异步范式 APM( Asynchronous Programming Model )说起。
在 .Net 4.0 之前,CLR 基础库中的异步 API 基本上都是 BeginXXX
与 EndXXX
的写法,调用异步 API 都需要先调用 BeginXXX
获取一个 IAsyncResult
对象,然后可以用于轮询其中的 IsCompleted
属性,或者使用 AsyncCallback
实现异步调用。也可以使用其中的 AsyncWaitHandle
用来同步等待。最后检查到完成异步操作之后,使用 EndXXX
获取最后的结果。
想想就觉得麻烦,这还是只是调用方,如果你要写基于 APM 的 API,那就更蛋疼了,代码被拆分在了不同的地方,当十几个异步 API 一起写,简直酸爽。
而现在的基于 Task + async/await 的异步范式 TAP( Task-based Asynchronous Pattern
)算是拯救了 C# 程序员,由编译器将链式的 Task 调用的异步代码再拼装起来,让其看上去就像同步调用一样。基于 TAP 的异步方法需要返回值是 Task
或者带返回值的 Task<T>
(还有带进度的 Task),总之就是一个包装。
而调用方想要异步等待一个 Task
必须在函数上标记 async
关键字,而且被标记了 async
关键字的函数要么没有返回值要么也需要返回一个 Task
。所以 async
是具有传染性的,往往由于调用了一个 async
方法,导致了后续所有的调用都需要是 async
的。其传染性,就从根本上就决定的,还是由于函数不可能正常执行到一半就中断了,所以所有异步操作都需要一个 Task
作为包装,当遇到了一个 await
的时候,编译器就将剩下的操作打包成要给 Task
作为返回,并且多个 await
能够通过 ContinueWith
形成链式调用,很像没有 async/await 的 Promise。
为了打破这个传染性,可以使用没有返回值的 async
函数,但是没有返回值的 async
函数被其他函数调用时就没有异步的特性。比如下面的程序仅仅会输出 Main 就结束了。因为 Test2 方法在执行到 await Test();
还没等 Test 方法异步调用结束时就已经把后续操作放入同步上下文中返回了。Main 函数并不知道 Test 有异步调用,就打印出 Main 结束程序了。
using System; using System.Threading.Tasks; namespace Kanro { class Program { static void Main(string[] args) { Test2(); Console.WriteLine("Main"); } static async Task Test() { await Task.Delay(1000); Console.WriteLine("Test"); } static async void Test2() { await Test(); Console.WriteLine("Test2"); } } }
还有一种方式可以用于打破 async
的传染性,就是使用 Task.Wait()
将异步调用转换为同步调用,阻塞当前线程。这样就将异步的病毒截至在这里了,算是比较常用的一种手段。
yield 与异步
各种异步方案,都是为了解决函数中途无法返回的问题,像 C# 提出的直接将函数直接分成两半(APM式),和采用立即返回一个 Task
包装(TAP式)都是为了中途就把线程从当前函数弄出去,干别的事情。
现代语言几乎都会有一个迭代器 yield 的实现,在 ES2015 中还没有 Promise + async/await 的异步实现式,基本上所有的 JS 库的异步都是采用 yield 实现。
yield 能够解决异步方案最大的难题(中途返回),举个例子
static IEnumerator<String> YieldTest() { yield return "1"; yield return "2"; yield return "3"; yield return "4"; }
yield 能让一个函数返回多次,当第一次返回后,第二次返回会紧接着上一次返回的地方执行,然后整个东西被打包成为了一个枚举器,每次调用 Next
方法时,就会接着上一次返回的地方执行。
Kotlin 中的协程
在最近发布的 Kotlin 1.3.0 中,Kotlin Coroutines 库也是正式 release 了,可喜可贺。随之而来的还有黑魔法的 Contract 功能和不知道怎么吐槽 JVM 的 Unsigned 类型。
首先来看看 Kotlin 中的协程最核心的关键字 suspend
,这个关键字表示标记有这个关键字的 function 或者 lambda 是 可中断
的。
注意到了吗?可中断,在上面我们说过了,作为异步最重要的就是实现函数的中途返回,也就是分割函数,只要实现了分割函数,就可以实现异步,从而实现协程。
然后更有意思的事情是, suspend
关键字只能被同样具有 suspend
关键字标记的函数调用。分割函数,传染性,简直和 async
关键字一模一样。
在文章的开头,我们讲到了同事对于 Kotlin 协程的猜想,是在编译器不会对代码额外的修改得出的结论,但是目前看来既然具有 suspend
关键字,说明编译器还是会有一些处理的。
接下来的一个例子验证了我的猜想
package io.kanro import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.async import kotlinx.coroutines.awaitAll import kotlinx.coroutines.delay import kotlinx.coroutines.launch fun main(args: Array<String>){ GlobalScope.launch { val jobA = async { println("${Thread.currentThread().name} JobA start") delay(1000) // Do some work println("${Thread.currentThread().name} JobA end") } val jobB = async { println("${Thread.currentThread().name} JobB start") delay(2000) // Do some work println("${Thread.currentThread().name} JobB end") } awaitAll(jobA, jobB) } Thread.sleep(5000) }
如果是在 await
方法中进入 EventLoop 的话,那么就回避了分割函数的问题,在需要分割的地方产生一次函数调用,而这个函数里面可以做任何事情,也就相当于分割了函数。但是无论无何,这些都还是在一次函数调用中,也就是整个调用都是在一个线程中。那么在一次 async
方法中的任何地方,线程应该都是同一个才对。
上面的代码运行下面却是这样的结果:
DefaultDispatcher-worker-1 JobA start DefaultDispatcher-worker-3 JobB start DefaultDispatcher-worker-1 JobA end DefaultDispatcher-worker-4 JobB end
令我和同事两个人大跌眼镜。说明 jobB 中第一次打印和第二次打印并不在同一个线程,那么一定有地方将这个函数做切割了,并且编译器也一定参与了这个过程。
百思不得其解的时候,我决定直接看这段函数的字节码,从中分析其原理。我从中提取了其中 jobB 的 lambda 对象的字节码一探究竟。
// ================io/kanro/MainKt$main$1$jobB$1.class ================= // class version 50.0 (50) // access flags 0x30 // signature Lkotlin/coroutines/jvm/internal/SuspendLambda;Lkotlin/jvm/functions/Function2<Lkotlinx/coroutines/CoroutineScope;Lkotlin/coroutines/Continuation<-Lkotlin/Unit;>;Ljava/lang/Object;>; // declaration: io/kanro/MainKt$main$1$jobB$1 extends kotlin.coroutines.jvm.internal.SuspendLambda implements kotlin.jvm.functions.Function2<kotlinx.coroutines.CoroutineScope, kotlin.coroutines.Continuation<? super kotlin.Unit>, java.lang.Object> final class io/kanro/MainKt$main$1$jobB$1 extends kotlin/coroutines/jvm/internal/SuspendLambda implements kotlin/jvm/functions/Function2 { // access flags 0x2 private Lkotlinx/coroutines/CoroutineScope; p$ // access flags 0x11 public final invokeSuspend(Ljava/lang/Object;)Ljava/lang/Object; @Lorg/jetbrains/annotations/Nullable;() // invisible @Lorg/jetbrains/annotations/NotNull;() // invisible, parameter 0 L0 INVOKESTATIC kotlin/coroutines/intrinsics/IntrinsicsKt.getCOROUTINE_SUSPENDED ()Ljava/lang/Object; L1 LINENUMBER 17 L1 ASTORE 4 ALOAD 0 GETFIELD io/kanro/MainKt$main$1$jobB$1.label : I TABLESWITCH 0: L2 1: L3 default: L4 L2 ALOAD 1 DUP INSTANCEOF kotlin/Result$Failure IFEQ L5 CHECKCAST kotlin/Result$Failure GETFIELD kotlin/Result$Failure.exception : Ljava/lang/Throwable; ATHROW L5 POP L6 ALOAD 0 GETFIELD io/kanro/MainKt$main$1$jobB$1.p$ : Lkotlinx/coroutines/CoroutineScope; ASTORE 2 L7 LINENUMBER 18 L7 NEW java/lang/StringBuilder DUP INVOKESPECIAL java/lang/StringBuilder.<init> ()V INVOKESTATIC java/lang/Thread.currentThread ()Ljava/lang/Thread; DUP LDC "Thread.currentThread()" INVOKESTATIC kotlin/jvm/internal/Intrinsics.checkExpressionValueIsNotNull (Ljava/lang/Object;Ljava/lang/String;)V INVOKEVIRTUAL java/lang/Thread.getName ()Ljava/lang/String; INVOKEVIRTUAL java/lang/StringBuilder.append (Ljava/lang/String;)Ljava/lang/StringBuilder; LDC " JobB start" INVOKEVIRTUAL java/lang/StringBuilder.append (Ljava/lang/String;)Ljava/lang/StringBuilder; INVOKEVIRTUAL java/lang/StringBuilder.toString ()Ljava/lang/String; ASTORE 3 L8 GETSTATIC java/lang/System.out : Ljava/io/PrintStream; ALOAD 3 INVOKEVIRTUAL java/io/PrintStream.println (Ljava/lang/Object;)V L9 L10 LINENUMBER 19 L10 LDC 2000 ALOAD 0 ALOAD 0 ICONST_1 PUTFIELD io/kanro/MainKt$main$1$jobB$1.label : I INVOKESTATIC kotlinx/coroutines/DelayKt.delay (JLkotlin/coroutines/Continuation;)Ljava/lang/Object; L11 DUP ALOAD 4 IF_ACMPNE L12 L13 LINENUMBER 17 L13 ALOAD 4 ARETURN L3 ALOAD 1 DUP INSTANCEOF kotlin/Result$Failure IFEQ L14 CHECKCAST kotlin/Result$Failure GETFIELD kotlin/Result$Failure.exception : Ljava/lang/Throwable; ATHROW L14 POP ALOAD 1 L12 LINENUMBER 20 L12 POP L15 NEW java/lang/StringBuilder DUP INVOKESPECIAL java/lang/StringBuilder.<init> ()V INVOKESTATIC java/lang/Thread.currentThread ()Ljava/lang/Thread; DUP LDC "Thread.currentThread()" INVOKESTATIC kotlin/jvm/internal/Intrinsics.checkExpressionValueIsNotNull (Ljava/lang/Object;Ljava/lang/String;)V INVOKEVIRTUAL java/lang/Thread.getName ()Ljava/lang/String; INVOKEVIRTUAL java/lang/StringBuilder.append (Ljava/lang/String;)Ljava/lang/StringBuilder; LDC " JobB end" INVOKEVIRTUAL java/lang/StringBuilder.append (Ljava/lang/String;)Ljava/lang/StringBuilder; INVOKEVIRTUAL java/lang/StringBuilder.toString ()Ljava/lang/String; ASTORE 3 L16 GETSTATIC java/lang/System.out : Ljava/io/PrintStream; ALOAD 3 INVOKEVIRTUAL java/io/PrintStream.println (Ljava/lang/Object;)V L17 L18 LINENUMBER 21 L18 GETSTATIC kotlin/Unit.INSTANCE : Lkotlin/Unit; ARETURN L4 NEW java/lang/IllegalStateException DUP LDC "call to 'resume' before 'invoke' with coroutine" INVOKESPECIAL java/lang/IllegalStateException.<init> (Ljava/lang/String;)V ATHROW L19 LOCALVARIABLE this Lio/kanro/MainKt$main$1$jobB$1; L0 L19 0 LOCALVARIABLE result Ljava/lang/Object; L0 L19 1 MAXSTACK = 5 MAXLOCALS = 5 @Lkotlin/coroutines/jvm/internal/DebugMetadata;(f="Main.kt", l={17, 20}, i={}, s={}, n={}, m="invokeSuspend", c="io/kanro/MainKt$main$1$jobB$1") // access flags 0x0 <init>(Lkotlin/coroutines/Continuation;)V ALOAD 0 ICONST_2 ALOAD 1 INVOKESPECIAL kotlin/coroutines/jvm/internal/SuspendLambda.<init> (ILkotlin/coroutines/Continuation;)V RETURN MAXSTACK = 3 MAXLOCALS = 2 // access flags 0x0 I label // access flags 0x11 // signature (Ljava/lang/Object;Lkotlin/coroutines/Continuation<*>;)Lkotlin/coroutines/Continuation<Lkotlin/Unit;>; // declaration: kotlin.coroutines.Continuation<kotlin.Unit> create(java.lang.Object, kotlin.coroutines.Continuation<?>) public final create(Ljava/lang/Object;Lkotlin/coroutines/Continuation;)Lkotlin/coroutines/Continuation; @Lorg/jetbrains/annotations/NotNull;() // invisible @Lorg/jetbrains/annotations/Nullable;() // invisible, parameter 0 @Lorg/jetbrains/annotations/NotNull;() // invisible, parameter 1 L0 ALOAD 2 LDC "completion" INVOKESTATIC kotlin/jvm/internal/Intrinsics.checkParameterIsNotNull (Ljava/lang/Object;Ljava/lang/String;)V NEW io/kanro/MainKt$main$1$jobB$1 DUP ALOAD 2 INVOKESPECIAL io/kanro/MainKt$main$1$jobB$1.<init> (Lkotlin/coroutines/Continuation;)V ASTORE 3 ALOAD 1 CHECKCAST kotlinx/coroutines/CoroutineScope ALOAD 3 ALOAD 1 CHECKCAST kotlinx/coroutines/CoroutineScope PUTFIELD io/kanro/MainKt$main$1$jobB$1.p$ : Lkotlinx/coroutines/CoroutineScope; ALOAD 3 ARETURN L1 LOCALVARIABLE this Lkotlin/coroutines/jvm/internal/BaseContinuationImpl; L0 L1 0 LOCALVARIABLE value Ljava/lang/Object; L0 L1 1 LOCALVARIABLE completion Lkotlin/coroutines/Continuation; L0 L1 2 MAXSTACK = 3 MAXLOCALS = 4 // access flags 0x11 public final invoke(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object; ALOAD 0 ALOAD 1 ALOAD 2 CHECKCAST kotlin/coroutines/Continuation INVOKEVIRTUAL io/kanro/MainKt$main$1$jobB$1.create (Ljava/lang/Object;Lkotlin/coroutines/Continuation;)Lkotlin/coroutines/Continuation; CHECKCAST io/kanro/MainKt$main$1$jobB$1 GETSTATIC kotlin/Unit.INSTANCE : Lkotlin/Unit; INVOKEVIRTUAL io/kanro/MainKt$main$1$jobB$1.invokeSuspend (Ljava/lang/Object;)Ljava/lang/Object; ARETURN MAXSTACK = 3 MAXLOCALS = 3 @Lkotlin/Metadata;(mv={1, 1, 13}, bv={1, 0, 3}, k=3, d1={"\u0000\u000e\n\u0000\n\u0002\u0010\u0002\n\u0002\u0018\u0002\n\u0002\u0008\u0002\u0010\u0000\u001a\u00020\u0001*\u00020\u0002H\u008a@\u00f8\u0001\u0000\u00a2\u0006\u0004\u0008\u0003\u0010\u0004"}, d2={"<anonymous>", "", "Lkotlinx/coroutines/CoroutineScope;", "invoke", "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;"}) OUTERCLASS io/kanro/MainKt$main$1 invokeSuspend (Ljava/lang/Object;)Ljava/lang/Object; // access flags 0x18 final static INNERCLASS io/kanro/MainKt$main$1$jobB$1 null null // access flags 0x18 final static INNERCLASS io/kanro/MainKt$main$1 null null // compiled from: Main.kt // debug info: SMAP Main.kt Kotlin *S Kotlin *F + 1 Main.kt io/kanro/MainKt$main$1$jobB$1 *L 1#1,25:1 *E }
可以看到这个 lambda 和普通的 lambda 不太一样,是继承自了 kotlin.coroutines.jvm.internal.SuspendLambda
的 lambda 对象,其中有两个主要方法 invoke
和 invokeSuspend
,其中 invoke
是一般 lambda 都有的方法,但是在这里只是对 invokeSuspend
的一个包装,获取了当前协程 Continuation
对象,传入 invokeSuspend
。
// access flags 0x11 public final invoke(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object; ALOAD 0 ALOAD 1 ALOAD 2 CHECKCAST kotlin/coroutines/Continuation INVOKEVIRTUAL io/kanro/MainKt$main$1$jobB$1.create (Ljava/lang/Object;Lkotlin/coroutines/Continuation;)Lkotlin/coroutines/Continuation; CHECKCAST io/kanro/MainKt$main$1$jobB$1 GETSTATIC kotlin/Unit.INSTANCE : Lkotlin/Unit; INVOKEVIRTUAL io/kanro/MainKt$main$1$jobB$1.invokeSuspend (Ljava/lang/Object;)Ljava/lang/Object; ARETURN MAXSTACK = 3 MAXLOCALS = 3
而重点就在 invokeSuspend
函数中,我们进行逐一分析。
// access flags 0x11 public final invokeSuspend(Ljava/lang/Object;)Ljava/lang/Object; @Lorg/jetbrains/annotations/Nullable;() // invisible @Lorg/jetbrains/annotations/NotNull;() // invisible, parameter 0 L0 INVOKESTATIC kotlin/coroutines/intrinsics/IntrinsicsKt.getCOROUTINE_SUSPENDED ()Ljava/lang/Object; L1 LINENUMBER 17 L1 ASTORE 4 ALOAD 0 GETFIELD io/kanro/MainKt$main$1$jobB$1.label : I TABLESWITCH 0: L2 1: L3 default: L4
首先这个函数通过 kotlin.coroutines.intrinsics.IntrinsicsKt.COROUTINE_SUSPENDED
获取到了一个协程中断对象,存入本地变量 4 中,然后获取当前的 lambda 的 label
字段,判断其状态,当值为 0 时跳转到 L2,为 1 则跳转到 L3,其他情况则跳转 L4。
我们暂时默认先跳转到 L2,L2 和 L5 只是判断一下传入函数的对象是不是失败了,如果失败了就抛出异常,紧接着 L6 将当前 lambda 对象的 CoroutineScope 存入本地变量 2。
L2 ALOAD 1 DUP INSTANCEOF kotlin/Result$Failure IFEQ L5 CHECKCAST kotlin/Result$Failure GETFIELD kotlin/Result$Failure.exception : Ljava/lang/Throwable; ATHROW L5 POP L6 ALOAD 0 GETFIELD io/kanro/MainKt$main$1$jobB$1.p$ : Lkotlinx/coroutines/CoroutineScope; ASTORE 2
L7 LINENUMBER 18 L7 NEW java/lang/StringBuilder DUP INVOKESPECIAL java/lang/StringBuilder.<init> ()V INVOKESTATIC java/lang/Thread.currentThread ()Ljava/lang/Thread; DUP LDC "Thread.currentThread()" INVOKESTATIC kotlin/jvm/internal/Intrinsics.checkExpressionValueIsNotNull (Ljava/lang/Object;Ljava/lang/String;)V INVOKEVIRTUAL java/lang/Thread.getName ()Ljava/lang/String; INVOKEVIRTUAL java/lang/StringBuilder.append (Ljava/lang/String;)Ljava/lang/StringBuilder; LDC " JobB start" INVOKEVIRTUAL java/lang/StringBuilder.append (Ljava/lang/String;)Ljava/lang/StringBuilder; INVOKEVIRTUAL java/lang/StringBuilder.toString ()Ljava/lang/String; ASTORE 3 L8 GETSTATIC java/lang/System.out : Ljava/io/PrintStream; ALOAD 3 INVOKEVIRTUAL java/io/PrintStream.println (Ljava/lang/Object;)V
L7 和 L8 都没有什么问题,就是执行 println("${Thread.currentThread().name} JobA start")
这一句,然后 L9 和 L10 也没有特殊的地方,也是执行 delay(1000)
的操作。
L9 L10 LINENUMBER 19 L10 LDC 2000 ALOAD 0 ALOAD 0 ICONST_1 PUTFIELD io/kanro/MainKt$main$1$jobB$1.label : I INVOKESTATIC kotlinx/coroutines/DelayKt.delay (JLkotlin/coroutines/Continuation;)Ljava/lang/Object; L11 DUP ALOAD 4 IF_ACMPNE L12 L13 LINENUMBER 17 L13 ALOAD 4 ARETURN
重点来了!注意看 L11 和 L13!L11 把 delay 返回值和本地变量4 做了比较,如果不相等就跳转 L12,如果相等就进入 L13,将本地变量 4 返回。
所以这里应该是判断 delay 是否是立即已经完成了,如果完成了就直接去 L12,而 L12 就是一句打印,如果没有就返回了当前这个任务。
L3 ALOAD 1 DUP INSTANCEOF kotlin/Result$Failure IFEQ L14 CHECKCAST kotlin/Result$Failure GETFIELD kotlin/Result$Failure.exception : Ljava/lang/Throwable; ATHROW L14 POP ALOAD 1 L12 LINENUMBER 20 L12 POP L15 NEW java/lang/StringBuilder DUP INVOKESPECIAL java/lang/StringBuilder.<init> ()V INVOKESTATIC java/lang/Thread.currentThread ()Ljava/lang/Thread; DUP LDC "Thread.currentThread()" INVOKESTATIC kotlin/jvm/internal/Intrinsics.checkExpressionValueIsNotNull (Ljava/lang/Object;Ljava/lang/String;)V INVOKEVIRTUAL java/lang/Thread.getName ()Ljava/lang/String; INVOKEVIRTUAL java/lang/StringBuilder.append (Ljava/lang/String;)Ljava/lang/StringBuilder; LDC " JobB end" INVOKEVIRTUAL java/lang/StringBuilder.append (Ljava/lang/String;)Ljava/lang/StringBuilder; INVOKEVIRTUAL java/lang/StringBuilder.toString ()Ljava/lang/String; ASTORE 3 L16 GETSTATIC java/lang/System.out : Ljava/io/PrintStream; ALOAD 3 INVOKEVIRTUAL java/io/PrintStream.println (Ljava/lang/Object;)V L17 L18 LINENUMBER 21 L18 GETSTATIC kotlin/Unit.INSTANCE : Lkotlin/Unit; ARETURN
接下来的 L3 就是当上面 lable 的状态为 1 时的跳转地址,而 lable 为 1 的含义就是中间的异步 delay 完成了,然后这个时候会被调度器再次调用这个 lambda,然后就会执行到后续的操作,至于 最后一点的 L4 是状态错误的时候调用的。
L4 NEW java/lang/IllegalStateException DUP LDC "call to 'resume' before 'invoke' with coroutine" INVOKESPECIAL java/lang/IllegalStateException.<init> (Ljava/lang/String;)V ATHROW
所以总结一下,就是编译器看到了 suspend
虽然不是和 C# 一样使用语法糖把链式调用封装为同步调用,但是也是基于编译器将状态机隐藏起来了。所以 Kotlin 的协程也是具有编译器参与在其中,并且是基于状态机的做法。
以上所述就是小编给大家介绍的《Kotlin VS C# - 协程与异步》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!
猜你喜欢:- SpringBoot | :异步开发之异步调用
- 改进异步封装:处理带返回值的异步调用
- 异步发展流程 —— Generators + co 让异步更优雅
- 文件系统与异步操作——异步IO那些破事
- js异步从入门到放弃(四)- Generator 封装异步任务
- netty的Future异步回调难理解?手写个带回调异步框架就懂了
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
Heuristic Search
Stefan Edelkamp、Stefan Schrodl / Morgan Kaufmann / 2011-7-15 / USD 89.95
Search has been vital to artificial intelligence from the very beginning as a core technique in problem solving. The authors present a thorough overview of heuristic search with a balance of discussio......一起来看看 《Heuristic Search》 这本书的介绍吧!