Kotlin協程教程(2):協程做用域與各類builder們

做用域與上下文

協程做用域本質是一個接口,既然是一個接口,那麼它就能夠被某個類去實現(implement),實現它的那個類,也就具有了一些能力。數據庫

class MyClass: CoroutineScope {
    // MyClass就具有了CoroutineScope的一些能力
}

那麼它具有了哪些能力呢?網絡

固然是啓動協程的能力和中止協程的能力。除了runBlocking有一些特殊外,launch和async其實都是CoroutineScope的擴展方法,它們兩個都必須經過做用域才能調用。併發

好比咱們有一個界面,裏面有一些數據是須要經過網絡或者文件或者數據庫才能獲取的,咱們想經過協程去獲取它們,但因爲界面可能隨時會被關閉,咱們但願界面關閉的時候,協程就不要再去工做了。async

咱們能夠這樣寫ui

class MyClass: CoroutineScope by CoroutineScope(Dispatchers.Default) {

    fun doWork() {
        launch {
            for (i in 0..10) {
                println("MyClass launch1 $i -----")
                delay(100)
            }
        }
    }

    fun destroy() {
        (this as CoroutineScope).cancel()
    }
}

fun main() {

    val myClass = MyClass()

    // 由於myClass已是一個CoroutineScope對象了,固然也能夠經過這種方式來啓動協程
    myClass.launch {
        for (i in 0..10) {
            println("MyClass launch1 $i *****")
            delay(100)
        }
    }

    myClass.doWork()

    Thread.sleep(500) // 讓協程工做一會

    myClass.destroy() // myClass須要被回收了!

    Thread.sleep(500) // 等一會方便觀察輸出
}

當destroy被調用的時候,myClass的協程就都中止工做了,是否是很爽,很方便。這個設計將很是適合與在GUI程序上使用。this

如今來小小的回顧下上面說的,協程必需要在CoroutineScope中才能啓動,本質是launch和async是CoroutineScope的擴展方法,在一個協程做用域CoroutineScope中啓動的協程,都將受到這個做用域的管控,能夠經過這個做用域的對象來取消內部的全部協程。spa

協程做用域CoroutineScope的內部,又包含了一個協程上下文(CoroutineContext) 對象。線程

協程上下文對象中,是一個key-value的集合,其中,最重要的一個元素就是Job,它表示了當前上下文對應的協程執行單元。設計

它們的關係看起來就像是這樣的:3d

clipboard.png

另外,launch和async啓動後的協程,也是一個新的做用域,以下代碼,我構造了好幾個協程,並print出當前的Scope對象。

GlobalScope.launch {
    println("GlobalScope ${this.toString()}")
    launch {
        println("A ${this.toString()}")
        launch {
            println("A1 ${this.toString()}")
        }
    }

    launch {
        println("B ${this.toString()}")
    }
}

運行結果:

GlobalScope StandaloneCoroutine{Active}@714834a4
B StandaloneCoroutine{Active}@6be16ee2
A StandaloneCoroutine{Active}@6a716a81
A1 StandaloneCoroutine{Active}@64b699bf

可見,做用域啓動新協程也是一個新的做用域,它們的關係能夠並列,也能夠包含,組成了一個做用域的樹形結構。

clipboard.png

默認狀況下,每一個協程都要等待它的子協程所有完成後,才能結束本身。這種形式,就被稱爲結構化的併發

各類builder們

關於GlobalScope.launch,還有一個小特性,就是它更像一個守護線程,沒法使進程保活。具體來講,就是,若是進程中只有這樣一個守護線程,可能會被幹掉。

fun main()  {
    runBlocking {
        launch {
            for (i in 0 ..100) {
                delay(1000)
                println("hahaha")
            }
        }
    }
    
    println("qqqqqqqqqqqq")
}

上面的代碼,能夠正確的輸出一堆hahahah,最終會輸出qqqqqqqq。

但若是將launch換成GlobalScope.launch,就是另外一種效果了

fun main()  {
    runBlocking {
        GlobalScope.launch { // 注意這裏的變化
            for (i in 0 ..100) {
                delay(1000)
                println("hahaha")
            }
        }
    }

    println("qqqqqqqqqqqq")
}

運行結果爲:
直接輸出qqqqqqqqqqqqq,進程就結束了。

在官方文檔上,launch、async被稱爲coroutine builder,我想不嚴謹的擴大一下這個概念,將常用到的都成爲builder,我已經總結了它們的特性,列在下面的表格中:

clipboard.png

總結

協程做用域本質是一個接口,咱們能夠手動聲明這樣一個接口,也可讓一個類實現這個接口。在語義上,彷彿就像定義了一個做用域,但又巧妙的在這個做用域的範圍內,可使用啓動協程的方法了,啓動的協程也天然的綁定在這個做用域上。

新啓動的協程又會建立本身的做用域,能夠自由的組合和包含,外層的協程必需要等到內部的協程所有完成了,才能完成本身的,這即是結構化的併發。

協程做用域其實是綁定了一個Job對象,這個Job對象表示做用域內全部協程的執行單元,能夠經過這個Job對象取消內部的協程。

若是你喜歡這篇文章,歡迎點贊評論打賞
更多幹貨內容,歡迎關注個人公衆號:好奇碼農君

相關文章
相關標籤/搜索