協程做用域本質是一個接口,既然是一個接口,那麼它就能夠被某個類去實現(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
另外,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
可見,做用域啓動新協程也是一個新的做用域,它們的關係能夠並列,也能夠包含,組成了一個做用域的樹形結構。
默認狀況下,每一個協程都要等待它的子協程所有完成後,才能結束本身。這種形式,就被稱爲結構化的併發
關於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,我已經總結了它們的特性,列在下面的表格中:
協程做用域本質是一個接口,咱們能夠手動聲明這樣一個接口,也可讓一個類實現這個接口。在語義上,彷彿就像定義了一個做用域,但又巧妙的在這個做用域的範圍內,可使用啓動協程的方法了,啓動的協程也天然的綁定在這個做用域上。
新啓動的協程又會建立本身的做用域,能夠自由的組合和包含,外層的協程必需要等到內部的協程所有完成了,才能完成本身的,這即是結構化的併發。
協程做用域其實是綁定了一個Job對象,這個Job對象表示做用域內全部協程的執行單元,能夠經過這個Job對象取消內部的協程。
若是你喜歡這篇文章,歡迎點贊評論打賞
更多幹貨內容,歡迎關注個人公衆號:好奇碼農君