[譯] Coroutines: First things first

這是關於 協程的取消和異常 的一系列文章,寫的很不錯。一直準備翻譯來着,種種緣由給耽誤了,一直拖到如今。android

原文做者:manuelvicntweb

原文地址:Coroutines: First things firstasync

譯者:秉心說編輯器

該系列博客深刻探索了協程的取消和異常。取消 能夠避免進行預期之外的工做,從而節省內存和電量;合適的異常處理 能夠帶來良好的用戶體驗。做爲該系列另外兩篇文章的基礎,經過本文搞清楚協程的一些基本概念,例如 CoroutineScopeJobCoroutineContext 等,是很是重要的。函數

若是你更喜歡視頻,能夠看看 Florina Muntenescu 和我在 KotlinConf'19 上的演講。學習

https://www.youtube.com/watch?v=w0kfnydnFWI&feature=emb_logourl

CoroutineScope(協程做用域)

CoroutineScope 能夠幫助你追蹤任何經過 launchasync 啓動的協程。它們都是 CoroutineScope 的擴展函數。正在運行的協程能夠經過調用 scope.cancel() 在任意時間點中止。spa

不管你在 App 的任何頁面啓動協程,並控制其生命週期,都應該建立 CoroutineScope 。在 Android 中,KTX 類庫已經爲特定的生命週期類提供了 CoroutineScope,例如 viewModelScopelifecycleScope線程

建立 CoroutineScope 時須要給構造函數提供 CoroutineContext(協程上下文) 參數。下面的代碼演示瞭如何新建一個做用域和協程。翻譯

// Job 和 Dispatcher 合併在一塊兒做爲 CoroutineContext,稍後會進行說明
val scope = CoroutineScope(Job() + Dispatchers.Main)

val job = scope.launch {
    // 新協程
}
複製代碼

Job

Job 表明了一個協程。經過 launchasync 啓動的每個協程,都會返回一個 Job 實例來惟一標識,而且管理該協程的生命週期。如上一節所示,你能夠給 CoroutineScope 傳遞一個 Job 來控制它的生命週期。

CoroutineContext(協程上下文)

能夠翻譯成協程上下文。但我仍是用英文吧。

CoroutineContext 是定義協程行爲的一系列元素。它由如下幾部分組成:

  • Job,管理協程的生命週期
  • CoroutineDispatcher,分發任務到合適的線程
  • CoroutineName,協程的名稱,用於調試
  • CoroutineExceptionHandler,處理未捕獲的異常,這是第三篇文章的內容

一個新協程的 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 生命週期

Job 會經歷如下生命週期:

New, Active, Completing, Completed, Cancelling , Cancelled

經過 Job 的這幾個屬性能夠獲取它的狀態:isActiveisCancelledisCompleted

Job lifecycle
Job lifecycle

當協程處於 Active 狀態,失敗或者取消都會讓協程移動到 Cancelling 狀態(isActive = false, isCancelled = true)。當協程中的全部子協程都完成了任務,協程將會進入 Cancelled 狀態 (isCompleted = true) 。

關於 Parent CoroutineContext

在協程的繼承結構中,每個協程都會有一個父親,這個父親多是 CoroutineScope 或者另外一個協程。 可是子協程最終的父 CoroutineContext 可能和其父親本來的 CoroutineContext 不同。

父 CoroutineContext 的計算公式以下:

Parent context = Defaults + 繼承的 CoroutineContext + arguments

其中:

  • 其中一些元素具備默認值: CoroutineDispatcher 的默認值是 Dispatchers.DefaultCoroutineName的默認值是 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 排版

相關文章
相關標籤/搜索