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是可以做到的。

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