Coroutines : First things first

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

原文作者:manuelvicnt

原文地址:Coroutines: First things first

譯者:秉心說

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

如果你更喜歡視頻,可以看看 Florina Muntenescu 和我在 KotlinConf'19 上的演講。

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

CoroutineScope(協程作用域)

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

無論你在 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

當協程處於 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 類型。

現在你已經瞭解了協程的基礎知識,在系列的後面兩篇文章中學習更多 取消和異常 的知識吧!


今天的文章就到這裏了,這個系列還有三篇文章,都很精彩,掃描下方二維碼持續關注吧!

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