【譯】Koltin協程:First things first

原文:Coroutines: First things first
做者:Manuel Vivo
譯者:Luckykelanjava

這一系列的博客文章深刻討論了Kotlin協程中的取消和異常。爲了不浪費內存和電池壽命,協程的取消相當重要;正確的異常處理是得到良好用戶體驗的關鍵。做爲本系列其餘兩部分(第2部分:取消,第3部分:異常)的基礎,這篇博客定義一些協程的核心概念,如協程做用域、Job和協程上下文等。android

協程做用域(CoroutineScope)

CoroutineScope 會跟蹤使用launch async 建立的任何協程(這些是CoroutineScope 上的擴展函數),而且能夠在任什麼時候間點經過調用scope.cancel() 取消正在此上下文中進行的協程。
每當咱們想在咱們的APP中啓動並控制一個協程的生命週期時,都應該建立一個 CoroutineScope 對象,在Android平臺,已有KTX庫提供了某些具備生命週期的類的 CoroutineScope ,如 viewModelScope lifecycleScope 。 在建立 CoroutineScope 時,它將一個 CoroutineContext 對象做爲構造參數,你可使用以下代碼建立新的協程做用域並使用它啓動一個協程:併發

// Job and Dispatcher組成一個新的CoroutineScope
// 咱們稍後進行討論
val scope = CoroutineScope(Job() + Dispatchers.Main)
val job = scope.launch {
    // new coroutine
}
複製代碼

Job

Job是協程的句柄,對於您經過 launch async 啓動的每一個協程,它都會返回一個Job實例,該實例惟一地標識了此協程並管理其生命週期。正如上面的代碼,您還能夠將Job對象傳遞給 CoroutineScope 來保持對此協程生命週期的控制。async

協程上下文(CoroutineContext)

CoroutineContext 是一組定義協程行爲的元素。它由:函數

  • Job — 控制協程的生命週期。
  • 協程調度器( CoroutineDispatcher )— 將協程分配到適當的線程。
  • CoroutineName — 協程的名稱,對調試頗有幫助。
  • CoroutineExceptionHandler — 處理未捕獲的異常,將在本系列的第3部分中介紹。

當咱們建立一個新的協程時,它的協程上下文都有什麼呢?咱們已經知道協程會返回一個新的Job 實例從而容許咱們控制它的生命週期,而其他的元素將今後協程的父元素(父協程或建立它的協程做用域)繼承[^譯者注]。
因爲協程做用域能夠建立協程,而且咱們能夠在協程中建立更多的協程,所以會造成一個隱式的任務層次結構,在下面的代碼中,除了使用協程做用域( CoroutineScope )建立一個新的協程外,還能夠看看如何在協程中建立更多的協程:spa

val scope = CoroutineScope(Job() + Dispatchers.Main)
val job = scope.launch {
    // CoroutineScope做爲父級的新協程
    val result = async {
        // 以協程做爲父級建立的新協程
    }.await()
}
複製代碼

這種層次結構的根一般爲最上層的協程做用域( CoroutineScope )。咱們能夠把該層次結構可視化以下: 線程

協程在任務層次結構中執行。父級能夠是CoroutineScope或另外一個協程

譯者注:這種任務的層次結構就是Koltin協程引覺得傲的結構化併發 — 父協程能夠控制和限制子線程的生命週期,子協程會繼承父協程的協程上下文 調試

Job的生命週期

Job 經歷如下一系列狀態:新建、活動、正在完成、已完成、正在取消和已取消狀態。雖然咱們沒法訪問狀態自己,但能夠訪問Job 的屬性: isActive isCancelled isCompleted code

若是協程處於活動狀態,則協程失敗或調用 job.cancel()方法將使Job處於取消狀態( isActive = false, iscancel = true )。一旦全部的子協程完成了他們的工做,協程將進入取消狀態而且 isCompleted = true

關於父級CoroutineContext的解釋

在協程的任務層次結構中,每一個協程都有一個父級,能夠是一個協程做用域,也能夠是另外一個協程。然而,協程繼承的父級協程上下文可能不一樣於父級自己的上下文,由於協程上下文是基於這個公式計算的cdn

父級上下文 = 默認值 + 繼承的上下文 + 參數

  • 某些元素具備默認值,如 Dispatchers.Default 是協程調度器( CoroutineDispatcher )的默認值,「coroutine」是 CoroutineName 的默認值。
  • 協程會繼承建立它的協程做用域或協程的 CoroutineContext
  • 在協程構建器中傳遞的參數會優先於繼承的 CoroutineContext 中的元素。

注意: CoroutineContext 可使用+運算符組合,因爲 CoroutineContext 是一組元素,所以會將加號右邊的元素覆蓋左側相同的元素以建立一個新的 CoroutineContext ,如 (Dispatchers.Main, 「name」) + (Dispatchers.IO) = (Dispatchers.IO, 「name」)

由scope建立的協程,在它的CoroutineContext中至少擁有這些元素,CoroutineName的值爲灰色緣由是它來自默認值

譯者注:根據CoroutineContext的源碼,CoroutineContext在內部使用鍵值對來維護元素,比較神奇的操做是,這些鍵值對的鍵對應着值類型的伴生對象,值爲這些類型的實例,因此同一類型的元素在同一個CoroutineContext中惟一。

如今咱們知道了一個新的協程的父級協程上下文是什麼,那麼它實際的協程上下文將是:

新協程的上下文 = 父級CoroutineContext + Job()

若是使用上圖的scope建立一個新協程,就像這樣:

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

那麼這個新協程的協程上下文和父級協程上下文將是什麼呢?請參見下圖的答案!

父級上下文中的Job永遠也不會與新協程的上下文的Job是同一實例,由於新協程始終會返回一個新的Job實例
咱們看到父級上下文中有 Dispatchers.IO ,而不是聲明 scope 時咱們傳入的 Dispatchers.Main ,由於它被協程構建器 launch 的參數覆蓋了。另外,請注意父級上下文中的Job實例是scope對象的Job,而新協程的實際Job實例是被從新分配的(綠色)。
咱們將在本系列的第三部分看到,協程上下文中有一個叫 SupervisorJob Job 實現, SupervisorJob 改變了 CoroutineScope 處理異常的方式。
相關文章
相關標籤/搜索