Kotlin协程简介(一) Hello,coroutines!-阿里云开发者社区

开发者社区> 开发与运维> 正文

Kotlin协程简介(一) Hello,coroutines!

简介: ## 协程的作用 协程并不是一个新鲜概念,相信大家都有所了解,它的好处是可以极大程度的复用线程,通过让线程满载运行,达到最大程度的利用CPU,进而提升应用性能。它和反应式编程一样都可以有效的提高资源的利用率,并且让我们脱离`callback hell`。目前JAVA里还没有原生的协程库(AJDK里对协程提供了支持)。Kotlin从1.1开始支持协程,不过目前还处于试验阶段,感兴趣的同学可以查看[

协程的作用

协程并不是一个新鲜概念,相信大家都有所了解,它的好处是可以极大程度的复用线程,通过让线程满载运行,达到最大程度的利用CPU,进而提升应用性能。它和反应式编程一样都可以有效的提高资源的利用率,并且让我们脱离callback hell。目前JAVA里还没有原生的协程库(AJDK里对协程提供了支持)。Kotlin从1.1开始支持协程,不过目前还处于试验阶段,感兴趣的同学可以查看这篇文档

如何在kotlin里使用协程

launch

由于协程还处于试验阶段,没有在Kotlin的标准库里提供,因此我们需要先引入一下相关的Jar包,Maven引用如下(其它同理)

<dependency>
    <groupId>org.jetbrains.kotlinx</groupId>
    <artifactId>kotlinx-coroutines-core</artifactId>
    <version>0.25.0</version>
</dependency>

然后直接上代码,先把官网上最简单的Hello world抄过来看看:

fun main(args: Array<String>) {
    launch { // 开启协程
        delay(1000L) // 阻塞协程,只能在协程里调用
        println("World!")  
    }
    println("Hello,") 
    Thread.sleep(3000L) // 等待协程执行
}

运行会得到如下结果

Hello,
World!

我们发现代码后边的 println("Hello,") 先执行了,可以肯定是launchdelay搞的鬼,如果隐藏在launch后边的是线程,也能得到完全同样的效果,不过这里隐藏的是协程,它在这里和线程有着同样的表现,确是非常轻量级的异步实现。

launch是协程的启动函数,注意它本身并不是一个协程,大括号里的部分才是协程的本体,我们知道大括号内部其实是个lambda表达式,而lambda表达式会被编译成一个匿名内部类的对象,反编译一下这个匿名内部类,大概是这样的:

final class Demo1Kt$main$1 extends CoroutineImpl implements Function2 {

   private CoroutineScope p$;


   @Nullable
   public final Object doResume(@Nullable Object data, @Nullable Throwable throwable) {
      Object var5 = IntrinsicsKt.getCOROUTINE_SUSPENDED();
      switch(super.label) {
      case 0:
         if(throwable != null) {
            throw throwable;
         }

         CoroutineScope var3 = this.p$;
         super.label = 1;
         if(DelayKt.delay$default(1000L, (TimeUnit)null, this, 2, (Object)null) == var5) {
            return var5;
         }
         break;
      case 1:
         if(throwable != null) {
            throw throwable;
         }
         break;
      default:
         throw new IllegalStateException("call to \'resume\' before \'invoke\' with coroutine");
      }

      String var4 = "World!";
      System.out.println(var4);
      return Unit.INSTANCE;
   }

   Demo1Kt$main$1(Continuation var1) {
      super(2, var1);
   }

   @NotNull
   public final Continuation create(@NotNull CoroutineScope $receiver, @NotNull Continuation continuation) {
      Intrinsics.checkParameterIsNotNull($receiver, "$receiver");
      Intrinsics.checkParameterIsNotNull(continuation, "continuation");
      Demo1Kt$main$1 var3 = new Demo1Kt$main$1(continuation);
      var3.p$ = $receiver;
      return var3;
   }

   @Nullable
   public final Object invoke(@NotNull CoroutineScope $receiver, @NotNull Continuation continuation) {
      Intrinsics.checkParameterIsNotNull($receiver, "$receiver");
      Intrinsics.checkParameterIsNotNull(continuation, "continuation");
      return ((Demo1Kt$main$1)this.create((CoroutineScope)$receiver, continuation)).doResume(Unit.INSTANCE, (Throwable)null);
   }
}

也不知道都是什么鬼(以后再说),我们只需要知道它实现了CoroutineImpl即可,CoroutineImpl实现了Continuation接口,Continuation代表了Kotlin里的协程,协程运行,其实就是运行Continuation

我们再看下launch的定义,

public fun launch(context: CoroutineContext , start: CoroutineStart , parent: Job?, onCompletion: CompletionHandler? , block: suspend CoroutineScope.() -> Unit):Job

先忽略它的参数,看看它的返回值Job,Job最常用的方法有三个,分别是start,joincancel,cancel没啥好说的,我们看看如何使用startjob:

fun main(args: Array<String>) = runBlocking(){
    val job = launch(EmptyCoroutineContext,CoroutineStart.LAZY) { // 开启协程
        println("execuse me!")
        doSomeDelay()
    }
    Thread.sleep(2000)
    println("Hello,")
    job.start()
    job.join()
}

suspend fun doSomeDelay() {
    delay(1000)
    println("World!")
}

这段代码的输出是

Hello,
execuse me!
World!

它除了调用了job的start和join方法外,还给launch传递了两个参数,引入了suspendrunBlocking两个关键字。我们一一看来:
CoroutineContext这玩意从名字看是协程上下文,我们先忽略,传个默认值,
CoroutineStart代表协程的启动方式,它是个枚举,如果它的值是LAZY,则协程不会立即启动,需要我们手工启动,这也是job.start()的意义。看输出,虽然先调用了launch方法,但是协程里的第一行代码哪怕主线程sleep了2000ms,也没有执行,而是一直等到调用start方法之后才执行。可以证明协程并没有随着launch的调用启动。

join方法,表示当前协程等待job所在的协程执行结束。它和线程里的join方法有同样的语义,大家对比着学习即可。这里需要注意的是join方法是suspend关键字修饰的,我们看下它的声明:

public abstract suspend fun join(): kotlin.Unit

一个协程方法必须被suspend修饰,且suspend修饰的方法必须在协程体里或者被suspend修饰的方法调用,比如第一段程序的代码,delay也是suspend修饰的,把delay那行放在launch外边,就会产生编译错误。这也是为什么我们把上边的代码里用了runBlocking方法。runBlocking会开启一个新的协程,并阻塞当前的线程,直到里面的代码执行完毕。

官方说法:

Runs new coroutine and blocks current thread interruptibly until its completion.

async

写到这里想必大家发现launch没法获取协程里代码的返回值,它有点像线程里的Runnable,但是很多情况下我们需要返回值,这时就需要async方法出场了,我们再来一个hello world:

fun main(args: Array<String>) = runBlocking {

    val deffered = async {
        doSomeThing()
    }
    println("Hello,")
    println(deffered.await())

}

suspend fun doSomeThing(): String {
    delay(1000)
    return "world!"
}

它的输出如下:

Hello,
world!

Deferred继承了Job,它除了拥有Job的方法外,还有一个await方法,它也是被suspend修饰的方法,因此我们在runBlocking里调用它。其它方面基本都和launch以及Job大同小异,就不赘述了。

总结

我们回顾一下,这里讲了

  1. 启动无返回值协程的launch方法和它的返回值Job,可以把它理解成线程里的Runnable
  2. 有返回值协程的async方法和它的返回值Deferred,可以把它理解成线程里的Callable
  3. 声明协程的关键字suspend
  4. 阻塞执行的runBlocking

有了这些,我们已经基本可以用协程实现功能了,更高级的东西下篇再说。

参考文献

https://kymjs.com/code/2017/11/24/01/
https://github.com/Kotlin/kotlinx.coroutines/blob/master/coroutines-guide.md#composing-suspending-functions
http://kotlinlang.org/docs/reference/coroutines.html#experimental-status-of-coroutines
https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.experimental/run-blocking.html

AD Time

国际惯例,给我们的读书群[独来读往]打个广告。欢迎喜欢读书的小伙伴加入我们,一起交流,一起成长.详见
https://lark.alipay.com/growth/notes/ewqntu

好久没打广告了o( ̄︶ ̄)o

版权声明:如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件至:developerteam@list.alibaba-inc.com 进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容。

分享:
开发与运维
使用钉钉扫一扫加入圈子
+ 订阅

集结各类场景实战经验,助你开发运维畅行无忧

其他文章