這是關於 協程的取消和異常 的一系列文章,寫的很不錯。一直準備翻譯來着,種種緣由給耽誤了,一直拖到如今。android
原文做者:manuelvicntweb
原文地址:Coroutines: First things firstasync
譯者:秉心說編輯器
該系列博客深刻探索了協程的取消和異常。取消 能夠避免進行預期之外的工做,從而節省內存和電量;合適的異常處理 能夠帶來良好的用戶體驗。做爲該系列另外兩篇文章的基礎,經過本文搞清楚協程的一些基本概念,例如 CoroutineScope
、Job
、CoroutineContext
等,是很是重要的。函數
若是你更喜歡視頻,能夠看看 Florina Muntenescu 和我在 KotlinConf'19 上的演講。學習
https://www.youtube.com/watch?v=w0kfnydnFWI&feature=emb_logourl
CoroutineScope
能夠幫助你追蹤任何經過 launch
和 async
啓動的協程。它們都是 CoroutineScope
的擴展函數。正在運行的協程能夠經過調用 scope.cancel()
在任意時間點中止。spa
不管你在 App 的任何頁面啓動協程,並控制其生命週期,都應該建立 CoroutineScope
。在 Android 中,KTX 類庫已經爲特定的生命週期類提供了 CoroutineScope
,例如 viewModelScope
和 lifecycleScope
。線程
建立 CoroutineScope
時須要給構造函數提供 CoroutineContext
(協程上下文) 參數。下面的代碼演示瞭如何新建一個做用域和協程。翻譯
// Job 和 Dispatcher 合併在一塊兒做爲 CoroutineContext,稍後會進行說明
val scope = CoroutineScope(Job() + Dispatchers.Main)
val job = scope.launch {
// 新協程
}
複製代碼
Job 表明了一個協程。經過 launch
和 async
啓動的每個協程,都會返回一個 Job
實例來惟一標識,而且管理該協程的生命週期。如上一節所示,你能夠給 CoroutineScope
傳遞一個 Job
來控制它的生命週期。
能夠翻譯成協程上下文。但我仍是用英文吧。
CoroutineContext
是定義協程行爲的一系列元素。它由如下幾部分組成:
一個新協程的 CoroutineContext
是什麼?咱們已經知道會建立一個新的 Job
來幫助咱們管理生命週期,剩下的元素將繼承自它的父親的 CoroutineContext
(多是另外一個協程,或者是建立它的 CoroutineScope)。
因爲 CoroutineScope
能夠建立協程,而且你能夠在一個協程內部建立多個協程。這就造成了一個隱式的層級結構。在下面的代碼中,除了使用 CoroutineScope
建立新協程以外,還展現瞭如何在一個協程中建立多個協程。
val scope = CoroutineScope(Job() + Dispatchers.Main)
val job = scope.launch {
// 這裏的新協程的父親是 scope
val result = async {
// 這裏的新協程的父親是上面的 scope.launch 啓動的協程
}.await()
}
複製代碼
層級結構的根一般是 CoroutineScope
。咱們能夠把層級結構想象成下面這樣:
Job
會經歷如下生命週期:
New, Active, Completing, Completed, Cancelling , Cancelled
經過 Job 的這幾個屬性能夠獲取它的狀態:isActive
、isCancelled
和 isCompleted
。
當協程處於 Active
狀態,失敗或者取消都會讓協程移動到 Cancelling
狀態(isActive = false
, isCancelled = true
)。當協程中的全部子協程都完成了任務,協程將會進入 Cancelled
狀態 (isCompleted = true) 。
在協程的繼承結構中,每個協程都會有一個父親,這個父親多是 CoroutineScope
或者另外一個協程。 可是子協程最終的父 CoroutineContext 可能和其父親本來的 CoroutineContext 不同。
父 CoroutineContext 的計算公式以下:
Parent context = Defaults + 繼承的 CoroutineContext + arguments
其中:
CoroutineDispatcher
的默認值是
Dispatchers.Default
,
CoroutineName
的默認值是
coroutine
CoroutineContext
是父親的 CoroutineContext
注意:多個 CoroutineContext
能夠經過 「+」 操做符合並。因爲 CoroutineContext
包含一系列元素,當建立新的 CoroutineContext
時,「+」 右側的元素將會覆蓋左側的元素。例如:(Dispatchers.Main, 「name」) + (Dispatchers.IO) = (Dispatchers.IO, 「name」)
。
經過此協程做用域建立的協程的 CoroutineContext 將至少包含上圖中這些元素。CoroutineName
是灰色的,由於它是默認值。
如今咱們知道一個新協程的父 CoroutineContext
是什麼了。它本身的 CoroutineContext
其實是這樣的:
New coroutine context = parent CoroutineContext + Job()
一般上面的協程做用域建立一個新的協程:
val job = scope.launch(Dispatchers.IO) {
//新協程
}
複製代碼
那麼它的父 CoroutineContext
和本身的 CoroutineContext
是什麼樣的呢?請看下面的圖片。
注意上下兩個 Job
並非同一個實例,新協程總會獲得一個新的 Job
實例。
最終的父 CoroutineContext
的協程調度器是 Dispatchers.IO
,由於它被協程構建器中的參數覆蓋了。(譯者注:scope.launch(Dispatchers.IO)
) 。
同時,注意父 CoroutineContext
中的 Job 實例就是 scope 的 Job 實例(紅色),而傳遞到新協程的 CoroutineContext
中的 Job 是一個新的實例(綠色)。
在系列第三篇文章中咱們將看到,CoroutineScope
能夠擁有其餘的 Job 實現類,SupervisorJob
,它會改變協程做用域的異常處理。所以,在這樣的 CoroutineScope
中建立的子協程也將繼承 SupervisorJob
類型的 Job 。可是,若是當父協程是另外一個協程的時候,將老是 Job 類型。
如今你已經瞭解了協程的基礎知識,在系列的後面兩篇文章中學習更多 取消和異常 的知識吧!
今天的文章就到這裏了,這個系列還有三篇文章,都很精彩,掃描下方二維碼持續關注吧!
本文使用 mdnice 排版