本文基于协程官方文档讲解,具体可查看here。
一、打印协程名和相应的线程
打印日志如下:
1.1、AS中java程序处理:
a. 打开协程Debug开关: 点击Edit Configurations,添加-Dkotlinx.coroutines.debug=on
b.打印日志时输出:Thread.currentThread().name 就可以显示出来了
fun printMsg(msg:String){
println("${Thread.currentThread().name} "+msg)
}
1.2、AS中android程序处理:
跟java程序一样,也要开启协程Debug开关,可在Application的oncreate方法中开启,同样日志打印也是要打印Thread.currentThread().name即可显示。
class App : Application() {
override fun onCreate() {
super.onCreate()
if (BuildConfig.DEBUG) {
System.setProperty("kotlinx.coroutines.debug", "on")
}
}
}
二、协程基础
2.1、"hello"先打印的🌰
新开子协程,设置新的调度器Dispatchers.IO
fun main()= runBlocking {
launch(Dispatchers.IO) {
printMsg("wowo")
delay(1000)
printMsg("world")
}
printMsg("hello")
}
解析:runBlocking只是一个非协程到协程的桥梁,它开启了一个协程(这里就是父协程),launch开启了一个子协程(设置了新的IO调度器),那子协程创建的时候就会切线程,delay挂起恢复时会再次切线程。
Q: 那为啥"hello"先打印,其余后打印?
一般我们认为,hello在主线程中肯定先打印,子协程调度到了一个异步线程,肯定会在后面打印。对吗?
那我们不设置新的调度器试试
fun main()= runBlocking {
launch { //直接继承父协程的上下文
printMsg("wowo")
delay(1000)
printMsg("world")
}
printMsg("hello")
}
都在主线程了,为啥还是hello先打印呢?(抓狂ing)来show kotlin byteCode找答案
public final class TestKt {
public static final void main() {
BuildersKt.runBlocking$default((CoroutineContext)null, (Function2)(new Function2((Continuation)null) {
private Object L$0;
int label;
@Nullable
public final Object invokeSuspend(@NotNull Object var1) {
Object var3 = IntrinsicsKt.getCOROUTINE_SUSPENDED();
switch(this.label) {
case 0:
ResultKt.throwOnFailure(var1);
CoroutineScope $this$runBlocking = (CoroutineScope)this.L$0;
BuildersKt.launch$default($this$runBlocking, (CoroutineContext)null, (CoroutineStart)null, (Function2)(new Function2((Continuation)null) {
int label;
@Nullable
public final Object invokeSuspend(@NotNull Object $result) {
Object var2 = IntrinsicsKt.getCOROUTINE_SUSPENDED();
switch(this.label) {
case 0:
ResultKt.throwOnFailure($result);
LoggerKt.printMsg("wowo");
this.label = 1;
if (DelayKt.delay(1000L, this) == var2) {
return var2;
}
break;
case 1:
ResultKt.throwOnFailure($result);
break;
default:
throw new IllegalStateException("call to 'resume' before 'invoke' with coroutine");
}
LoggerKt.printMsg("world");
return Unit.INSTANCE;
}
@NotNull
public final Continuation create(@Nullable Object value, @NotNull Continuation completion) {
......
}
public final Object invoke(Object var1, Object var2) {
......
}
}), 3, (Object)null);
//一定会在case 0处走完。
LoggerKt.printMsg("hello");
return Unit.INSTANCE;
default:
throw new IllegalStateException("call to 'resume' before 'invoke' with coroutine");
}
}
@NotNull
public final Continuation create(@Nullable Object value, @NotNull Continuation completion) {
.......
}
public final Object invoke(Object var1, Object var2) {
......
}
}), 1, (Object)null);
}
}
解析: 父子协程其实就是回调嵌套,带调度器和不带调度器只是入参不同而已,启动协程必然会调用resume(Unit),会触发invokeSuspend的调用,外层回调中的case 0就会走,暂且认为子回调里面的case 0执行肯定会晚一些。(基于目前的了解情况,未能找到create 和invoke方法调用的时机,逻辑未能自洽,有知道的朋友解释一下)所以"hello"先打印。
2.2、强制让"hello"后打印的🌰
2.2.1、使用coroutineScope实现
fun main()= runBlocking {
coroutineScope {
launch(Dispatchers.IO) {
printMsg("wowo")
delay(1000)
printMsg("world")
}
}
printMsg("hello")
}
或者:
fun main() = runBlocking {
doWork()
printMsg("hello")
}
suspend fun doWork() = coroutineScope {
launch(Dispatchers.IO) {
printMsg("wowo")
delay(1000)
printMsg("world")
}
}
同样看下show kotlin byteCode
public final class TestKt {
public static final void main() {
BuildersKt.runBlocking$default((CoroutineContext)null, (Function2)(new Function2((Continuation)null) {
int label;
@Nullable
public final Object invokeSuspend(@NotNull Object $result) {
Object var2 = IntrinsicsKt.getCOROUTINE_SUSPENDED();
switch(this.label) {
case 0:
ResultKt.throwOnFailure($result);
Function2 var10000 = (Function2)(new Function2((Continuation)null) {
private Object L$0;
int label;
@Nullable
public final Object invokeSuspend(@NotNull Object var1) {
Object var3 = IntrinsicsKt.getCOROUTINE_SUSPENDED();
switch(this.label) {
case 0:
ResultKt.throwOnFailure(var1);
CoroutineScope $this$coroutineScope = (CoroutineScope)this.L$0;
return BuildersKt.launch$default($this$coroutineScope, (CoroutineContext)Dispatchers.getIO(), (CoroutineStart)null, (Function2)(new Function2((Continuation)null) {
int label;
@Nullable
public final Object invokeSuspend(@NotNull Object $result) {
Object var2 = IntrinsicsKt.getCOROUTINE_SUSPENDED();
switch(this.label) {
case 0:
ResultKt.throwOnFailure($result);
LoggerKt.printMsg("wowo");
this.label = 1;
if (DelayKt.delay(1000L, this) == var2) {
return var2;
}
break;
case 1:
.......
}
LoggerKt.printMsg("world");
return Unit.INSTANCE;
}
@NotNull
public final Continuation create(@Nullable Object value, @NotNull Continuation completion) {
......
}
public final Object invoke(Object var1, Object var2) {
.....
}
}), 2, (Object)null);
default:
.....
@NotNull
public final Continuation create(@Nullable Object value, @NotNull Continuation completion) {
......
}
public final Object invoke(Object var1, Object var2) {
.....
}
});
this.label = 1;
//如果挂起,直接return了哈 !!!!!!!!!!!!!!!!
if (CoroutineScopeKt.coroutineScope(var10000, this) == var2) {
return var2;
}
break;
case 1:
ResultKt.throwOnFailure($result);
break;
default:
throw new IllegalStateException("call to 'resume' before 'invoke' with coroutine");
}
//如果case 0 挂起,就不会走这里了
LoggerKt.printMsg("hello");
return Unit.INSTANCE;
}
@NotNull
public final Continuation create(@Nullable Object value, @NotNull Continuation completion) {
.......
}
public final Object invoke(Object var1, Object var2) {
......
}
}), 1, (Object)null);
}
}
只要coroutineScope里面包裹的挂起了,那就让这个挂起的(或者挂起函数)先走。
if (CoroutineScopeKt.coroutineScope(var10000, this) == IntrinsicsKt.getCOROUTINE_SUSPENDED()) {
return IntrinsicsKt.getCOROUTINE_SUSPENDED();
}
2.2.2、使用join实现
fun main() = runBlocking {
val job = launch(Dispatchers.IO) {
printMsg("wowo")
delay(1000)
printMsg("world")
}
job.join()
printMsg("hello")
}
原理同上面,join()是个挂起函数,如真正能够挂起,那就先让join的job执行完。
if (job.join(this) == IntrinsicsKt.getCOROUTINE_SUSPENDED()) {
return IntrinsicsKt.getCOROUTINE_SUSPENDED();
}
三、协程是结构化的并发
结构化并发在协程里面就表示:父子协程之间是有结构的,只要有一个子协程还没跑完,父协程都不会结束,某种意义上,子协程更像是父协程的一个局部变量,当父协程作用域结束时,子协程一定运行完了。我们一定要理解的点是kotlin中写协程代码,别以为最后一行代码“hello”执行完了,就表示程序结束了,不一定的。
四、协程是轻量级的
官方每次这么说,都拿repeat和delay举例子
fun main() = runBlocking {
repeat(10000) {
launch(Dispatchers.Default) {
delay(1000)
printMsg("hello2")
}
}
printMsg("hello1")
}
开启10000个协程,去执行任务,执行很快。
协程delay对应Thread的sleep,前者其实是定时执行任务而已,线程切换少,后者执行时会让线程等待1s后再次运行。