协程(2) | launch启动协程

1,604 阅读5分钟

前言

记住前面所说的协程思维模型,我们来看看如何启动一个真实存在的协程。

正文

我们知道启动一个线程,只需要new Thread,然后调用其start()方法,便会在操作系统层面创建一个线程来执行其实现的run()方法中代码。

但是协程并没有一个类让我们来new或者继承,而是通过几个API来启动一个协程,所以这也给我们对比线程这种真实存在的东西有更抽象一点。

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代码块即是协程运行的代码块,也可以理解为这段lambda代码就协程

注意和线程概念一样,协程是一段程序,是一个动态执行的代码范围,比如上面lambda中的3行代码就是一个协程,在这个lambda中可以打印出协程的信息,当代码执行完后,协程也就结束了。

从这个角度来看,就更好理解"协程是运行在线程上的Task"这句话了,因为这个协程是运行在主线程上的Task。

非阻塞

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

image.png

我们可以发现虽然协程代码在前面,但是协程代码块后面的打印先执行,这里也就是"非阻塞"的表现。这个效果很像在协程那里开启了一个子线程去执行协程任务,然后主线程可以继续执行,关于具体的实现,我们后面细说。

协程和线程的关系

上面例子代码,我们都会在代码最后调用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,这是带接收者的函数类型参数。回忆一下带接收者的函数类型含义:带接收者的函数类型,就相当于该接收者的成员函数,可以调用其接收者的成员方法

    我们可以类比Kotlin标准库的apply方法,上面block参数为:block: suspend (CoroutineScope) -> Unit,而这个CoroutineScope也是我们扩展函数接收者对象。

    这也就说明在block中我们可以随意调用CoroutineScope中的成员方法。

    最后就是suspend关键字,这个表示是挂起函数,关于挂起函数我们后面细说,现在只需要知道挂起函数编译器会有特殊处理。

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

再结合block的类型,是返回Unit,我们也可以知道launch所启动的协程,是没有返回结果的,也印证了前面所说的"射箭模型"。

总结

如果本篇文章只需要记住一点,那就是launch开启的协程就像是"射出去的箭",在启动的地方"射出去",去干一些不需要结果、一劳永逸的任务,不会阻塞接下来程序的执行

从这篇文章的分析来看,我们终于真真切切地启动了一个协程,而且通过分析launch的函数定义,来分析了其返回值、带接收者的函数类型参数等,知道了有CoroutineScope的概念,只能在这个Scope下才可以用launch启动协程。