协程的作用
协程并不是一个新鲜概念,相信大家都有所了解,它的好处是可以极大程度的复用线程,通过让线程满载运行,达到最大程度的利用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,")
先执行了,可以肯定是launch
和delay
搞的鬼,如果隐藏在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
,join
和cancel
,cancel
没啥好说的,我们看看如何使用start
和job
:
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
传递了两个参数,引入了suspend
和runBlocking
两个关键字。我们一一看来: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
大同小异,就不赘述了。
总结
我们回顾一下,这里讲了
- 启动无返回值协程的
launch
方法和它的返回值Job
,可以把它理解成线程里的Runnable
- 有返回值协程的
async
方法和它的返回值Deferred
,可以把它理解成线程里的Callable
- 声明协程的关键字
suspend
- 阻塞执行的
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