协程粉碎计划 | launch启动协程

·  阅读 784
协程粉碎计划 | launch启动协程

一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第27天,点击查看活动详情

本系列专栏 # Kotlin协程专栏

前言

协程概念不再多说,到目前为止,我们知道了协程是非阻塞的,运行在线程上更轻量的Task,通过调试信息可以看得见我们创建的协程。

那本篇文章开始,正式介绍协程用法和原理。

正文

我们知道启动一个线程,只需要new Thread,然后调用其start()方法,便会在操作系统层面创建一个线程来执行其实现的run()方法中代码,但是协程并没有一个类让我们来new或者继承,而是通过几个固定的方法,本篇文章就先介绍使用最多的launch方法。

launch启动协程

话不多说,我们直接看下面代码:

fun main() {
    GlobalScope.launch {
        println("Hello World! 1")
        delay(1000L)
        println("Hello World! 2")
    }
    println("Hello World! 3")
    Thread.sleep(2000L)
}
复制代码

这里通过launch和后面的lambda其实就启动了一个协程,而且这个协程已经在运行了,或者可以这样说,lambda代码块即是协程运行的代码块,也可以理解为这段代码就协程。

这比我们创建和运行线程要简单多了,下面是线程创建:

fun main() {
    val thread = Thread{
        println("hello world")
    }
    thread.start()
}
复制代码

虽然说麻烦,但是线程有具体的Thread类,所以协程就有点抽象。

(备注:Kotlin是支持高级函数的,而高级函数是可以通过lambda表示,而高级函数的实现其实就是Kotlin内置的FunctionN接口,这个本质是不变的。)

非阻塞

还是上面的代码,我们执行结果如下:

image.png

会发现先执行的3,再执行的1和2,也就相当于这里的代码的执行顺序是不按照代码的顺序来执行的,其实这个也非常好理解,因为创建协程是需要时间的,而launch创建出来的协程就像new了一个子线程,互不干扰,所以3先执行是正常的。

而这种也说明launch创建的协程并不会阻塞当前线程的运行

协程和线程的关系

上面例子代码,我们都会在代码最后调用sleep休眠了2s,有没有考虑是为什么,我们来把那个sleep代码给删了:

fun main() {
    GlobalScope.launch() {
        println("Hello World! 1 ")
        delay(1000L)
        println("Hello World! 2 ")
    }
    println("Hello World! 3 ")
}
复制代码

执行结果如下图所示:

image.png

会发现当主线程销毁后,它创建的协程也不会再执行。所以这里sleep是为了主线程不会这么快退出

射箭模型

从上面launch创建的协程执行结果能看的出来,当这个协程被启动后,主线程并没有获取它执行的结果,而模式就类比于射箭,所以launch创建协程的模式如果构建思维模型的话,可以看成射箭模型

他们都有的共同点:

  • 箭一旦射出去了,目标就无法再改变;协程一旦被launch,那么它当中执行的任务也不会被中途改变

  • 箭如果命中了猎物,猎物不会自动送到我们手上来;launch的协程一旦任务完成了,即便有结果,也没办法直接返回被调用方

所以launch适合的场景是业务去执行,不需要得到其返回值的情况。

launch源码简析

这里并不真的去看launch的源码实现,而是简单看一下函数参数,简单了解一下,launch函数定义如下:

public fun CoroutineScope.launch(
    context: CoroutineContext = EmptyCoroutineContext,
    start: CoroutineStart = CoroutineStart.DEFAULT,
    block: suspend CoroutineScope.() -> Unit
): Job {
    ...
}
复制代码

这里其实很考察Kotlin语法的熟悉程度,我们来简单看一下:

  1. launch是一个扩展函数,它的接收者类型是CoroutineScope,这个表示协程的作用域,而Kotlin的扩展方法就表明launch就相当于CoroutineScope的成员方法。

  2. 第一个参数是CoroutineContext,这个是协程的上下文,默认值就是EmptyCoroutineContext,而这个参数也可以传递Dispatchers,来指定协程运行的线程池。

  3. 第二个参数是CoroutineStart,表示协程的启动模式,一般是DEFAULT表示立即执行,还有就是LAZY表示懒加载执行。

  4. 第三个参数是"suspend CoroutineScope.() -> Unit",这是一个函数类型,这表示什么呢 首先表示这个函数是suspend即挂起函数,其次是CoroutineScope类的成员方法或者扩展方法,最后它的参数类型是没有(没有参数),返回值是Unit

  5. 返回值是Job,它代表是协程的句柄,并不能返回协程的执行结果,这也就说明了launch启动的协程不能返回结果。

上面只是简单了解,而其中最难理解的就是block的函数类型,我们先来下面代码:

//定义一个扩展函数叫做testFun
suspend fun CoroutineScope.testFun() {
    
}
复制代码
//testFun变量的类型就是上面block类型变量
val testFun: suspend CoroutineScope.() -> Unit = CoroutineScope::testFun
//这个变量可以作为launch的参数
GlobalScope.launch(EmptyCoroutineContext, CoroutineStart.DEFAULT, testFun)
复制代码

这里的testFun的函数类型匹配成功,不仅仅要求是CoroutineScope的扩展(成员)方法,还必须要有suspend修饰,关于这个suspend关键字的作用,后面我们单独来说,意思就是这是一个挂起函数。

再结合前面知识Kotlin的lambda本质就是FunctionN接口,可以看我之前的文章:

juejin.cn/post/704662…

所以我们可以猜想一下,这里的lambda其实就是实现了一个FunctionN接口实例的匿名类对象,至于suspend是啥玩意,我们记住就可以,即launch的block的函数类型是suspend的。

我们来反编译一下代码:

public final class TestCoroutineKt {
   public static final void main() {
      BuildersKt.launch$default((CoroutineScope)GlobalScope.INSTANCE, (CoroutineContext)null, (CoroutineStart)null, (Function2)(new Function2((Continuation)null) {
         int label;

         @Nullable
         public final Object invokeSuspend(@NotNull Object $result) {
            Object var3 = IntrinsicsKt.getCOROUTINE_SUSPENDED();
            String var2;
            switch(this.label) {
            case 0:
               ResultKt.throwOnFailure($result);
               var2 = "Hello World! 1";
               System.out.println(var2);
               this.label = 1;
               if (DelayKt.delay(1000L, this) == var3) {
                  return var3;
               }
               break;
            case 1:
               ResultKt.throwOnFailure($result);
               break;
            default:
               throw new IllegalStateException("call to 'resume' before 'invoke' with coroutine");
            }

            var2 = "Hello World! 2";
            System.out.println(var2);
            return Unit.INSTANCE;
         }

         @NotNull
         public final Continuation create(@Nullable Object value, @NotNull Continuation completion) {
            Intrinsics.checkNotNullParameter(completion, "completion");
            Function2 var3 = new <anonymous constructor>(completion);
            return var3;
         }

         public final Object invoke(Object var1, Object var2) {
            return ((<undefinedtype>)this.create(var1, (Continuation)var2)).invokeSuspend(Unit.INSTANCE);
         }
      }), 3, (Object)null);
      String var0 = "Hello World! 3";
      System.out.println(var0);
      Thread.sleep(2000L);
   }

   // $FF: synthetic method
   public static void main(String[] var0) {
      main();
   }
}
复制代码

会发现上面调用lambda的地方是:

(Function2)(new Function2((Continuation)null){
    
}
复制代码

会发现里面有Function2接口的实例,这也就验证了我们的猜想,至于为什么会编译为Function2这种2个参数的接口,以及suspend为啥不见了,这些东西我们后面文章再探究。

总结

这里就记住一点,launch启动协程就如射箭,而实际业务中适合启动那种一劳永逸的任务,不需要获取执行结果。

分类:
Android
标签:
分类:
Android
标签:
收藏成功!
已添加到「」, 点击更改