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

  1. 在系列一中,我们已经对协程有了初步的了解,我们在此篇继续。前面我们是通过GlobalScope.launch{}这个协程建造器来创建协程的,它的特点就是:

    Launches a new coroutine without blocking the current thread.
    

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

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

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

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

  3. 直接来看代码:

    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
    
  4. 通过上面的阐述,相信大家对于这两种方式创建出来的协程的不同特点。那么如果我们把这两者进行嵌套使用呢?

    fun main() = runBlocking {
    
        GlobalScope.launch {
            delay(1000)
            println("world")
        }
    
        println("hello")
    
        delay(2000)
    
        println("welcome")
    
    }
    

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

  5. 我们再来注意一下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就是一个待执行的任务,并且能够被取消,有对应的生命周期:_New_、_Active_、_Completing_、_Cancelling_、_Cancelled_、_Completed_.如果非要类比的话,感觉有点像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),所以就能解释上面的输出结果了。读者可以按照我们前面的时序分析法来分析输出结果,我这里就不再赘述了。

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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章