协程系列(一) 基础知识

1,467 阅读3分钟

本文基于协程官方文档讲解,具体可查看here

一、打印协程名和相应的线程

打印日志如下:

image.png

1.1、AS中java程序处理:

a. 打开协程Debug开关: 点击Edit Configurations,添加-Dkotlinx.coroutines.debug=on image.png

image.png

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")
}

image.png 解析:runBlocking只是一个非协程到协程的桥梁,它开启了一个协程(这里就是父协程),launch开启了一个子协程(设置了新的IO调度器),那子协程创建的时候就会切线程,delay挂起恢复时会再次切线程。

Q: 那为啥"hello"先打印,其余后打印?
一般我们认为,hello在主线程中肯定先打印,子协程调度到了一个异步线程,肯定会在后面打印。对吗?
那我们不设置新的调度器试试

fun main()= runBlocking {
    launch {  //直接继承父协程的上下文
        printMsg("wowo")
        delay(1000)
        printMsg("world")
    }
    printMsg("hello")
}

image.png

都在主线程了,为啥还是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")
    }
}

image.png 同样看下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")
}

image.png 原理同上面,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后再次运行。