Kotlin学习系列之:协程的创建(二)

x33g5p2x  于2022-03-08 转载在 其他  
字(2.3k)|赞(0)|评价(0)|浏览(533)
  1. 在系列一中,我们已经对协程有了初步的了解,我们在此篇继续。前面我们是通过GlobalScope.launch{}这个协程建造器来创建协程的,它的特点就是:
Launches a new coroutine without blocking the current thread.

即不会阻塞当前的线程的运行。我们再来接触一个不一样的协程建造器: runBlocking{}

  1. 先来看看对于runBlocking{}的文档描述:

Runs a new coroutine and blocks the current thread interruptibly until its completion.

核心就是一个单词,block,会阻塞当前线程,这点是和.launch{}是大不相同的,即只有runBlocking中的代码执行完毕后,当前线程才会被唤醒。我们接下来通过代码来感受一下。

  • 直接来看代码:
fun main() {

    GlobalScope.launch {
        delay(1000)
        println("world")
    }

    println("hello")

    runBlocking {
        delay(2000)
    }

    println("welcome")

}

同样按照之前的时序发展方式,我们来分析一下这段代码:

  • 0s: 通过launch{}方式创建了一个协程并且开始delay;由于launch{}方式不会阻塞当前协程,所以会紧接着打印“hello”; 通过runBlocking{}的方式创建了一个协程并开始delay;由于runBlocking{}会阻塞线程,所以之后的代码此时不会得到执行
  • 1s: launch{}协程被唤醒,打印“world”
  • 2s: runBlocking{}协程被唤醒,然后该协程直接结束;当前线程被阻塞完毕,继续执行,打印“welcome”

所以最终的输出结果就是:

hello
(暂停1s)
world
(暂停1s)
welcome
  • 通过上面的阐述,相信大家对于这两种方式创建出来的协程的不同特点。那么如果我们把这两者进行嵌套使用呢?
fun main() = runBlocking {

    GlobalScope.launch {
        delay(1000)
        println("world")
    }

    println("hello")

    delay(2000)

    println("welcome")

}

输出的结果和之前的例子一样。但是将delay(2000)换成delay(500)结果就不一样了,大家可以去试试,原因的话我们在下一篇去解释。

  • 我们再来注意一下GlobalScope.launch{}:
public fun CoroutineScope.launch(
    context: CoroutineContext = EmptyCoroutineContext,
    start: CoroutineStart = CoroutineStart.DEFAULT,
    block: suspend CoroutineScope.() -> Unit
): Job {
    ....
}

会发现它实际上会返回一个Job类型的引用,何为Job?

A background job. Conceptually, a job is a cancellable thing with a life-cycle that culminates in its completion.

job就是一个待执行的任务,并且能够被取消,有对应的生命周期:NewActiveCompletingCancellingCancelledCompleted.如果非要类比的话,感觉有点像Runnable,并且也是没有返回值的。类似的还有个Deferred,这个是有返回值的,那么这个就有点像Future,后面有专门的篇章再去介绍。今天我们只需要关注它的一个方法:join()

1.Suspends the coroutine until this job is complete. 
2.Note that the job becomes complete only when all its children are complete.

这是我从文档上摘取出来的两句:1. 会挂起协程(job的父协程),直到该job执行完毕;2. 只有当某个协程的所有子协程都执行完毕后,才意味着该协程执行完毕。有点抽象,我们来看段代码:

fun main() = runBlocking {

    val job = GlobalScope.launch {
        delay(1000)
        println("world")
    }

    println("hello")

    job.join()

    println("welcome")

}

一运行:

hello
(等待1s)
world
welcome

这里先通过runBlocking创建了协程A,在协程A作用域里又通过launch创建了协程B,这样协程B就成了协程A的子协程。这里的job是子协程B的,所以当执行到job.join()这一行时,会挂起父协程A。只有当job执行完毕后,父协程的执行流程才会恢复(resume),所以就能解释上面的输出结果了。读者可以按照我们前面的时序分析法来分析输出结果,我这里就不再赘述了。

  • 那么job.join()的现实意义在哪?从某种意义上讲,实现了两个协程间的协作与同步。之前我们的所有代码都是通过delay的方式来达到这种顺序输出的效果,实际开发中我们并不知道这个job的完成需要多久,比如就是常见的网络请求,你根本不知道请求啥时候会返回(job的delay可以看作是这个操作的模拟),就不能在父协程中通过delay的方式来实现同步。但是毫无疑问,join是可以做到的。

相关文章