協程中的取消和異常 | 核心概念介紹

在以前的文章裏,咱們爲各位開發者分享了在 Android 中使用協程的一些基礎知識,包括在 Android 協程的 背景介紹上手指南代碼實戰。本次系列文章 "協程中的取消和異常" 也是 Android 協程相關的內容,咱們將與你們深刻探討協程中關於取消操做和異常處理的知識點和技巧。html

當咱們須要避免多餘的處理來減小內存浪費並節省電量時,取消操做就顯得尤其重要;而妥善的異常處理也是提升用戶體驗的關鍵。本篇是另外兩篇文章的基礎 (第二篇和第三篇將爲你們分別詳解協程取消操做和異常處理), 因此有必要先講解一些協程的核心概念,好比 CoroutineScope (協程做用域)、Job (任務) 和 CoroutineContext (協程上下文),這樣咱們纔可以進行更深刻的學習。android

CoroutineScope

CoroutineScope 會追蹤每個您經過 launch 或者 async 建立的協程 (這兩個是 CoroutineScope 的擴展函數)。任什麼時候候均可經過調用 scope.cancel() 來取消正在進行的工做 (正在運行的協程)。git

當您但願在應用程序的某一個層次開啓或者控制協程的生命週期時,您須要建立一個 CoroutineScope。對於一些平臺,好比 Android,已經有 KTX 這樣的庫 在一些類的生命週期裏提供了 CoroutineScope,好比 viewModelScope.viewModelScope:kotlinx.coroutines.CoroutineScope) 和 lifecycleScopegithub

當建立 CoroutineScope 的時候,它會將 CoroutineContext 做爲構造函數的參數。您能夠經過下面代碼建立一個新的 scope 和協程:api

//Job 和 Dispatcher 已經被集成到了 CoroutineContext
//後面咱們詳細介紹
val scope = CoroutineScope(Job() + Dispatchers.Main)

val job = scope.launch {
    //新的協程
}

Job

Job 用於處理協程。對於每個您所建立的協程 (經過 launch 或者 async),它會返回一個 Job 實例,該實例是協程的惟一標識,而且負責管理協程的生命週期。正如咱們上面看到的,您能夠將 Job 實例傳遞給 CoroutineScope 來控制其生命週期。jvm

CoroutineContext

CoroutineContext 是一組用於定義協程行爲的元素。它由以下幾項構成:async

  • Job: 控制協程的生命週期;
  • CoroutineDispatcher: 向合適的線程分發任務;
  • CoroutineName: 協程的名稱,調試的時候頗有用;
  • CoroutineExceptionHandler: 處理未被捕捉的異常,在將來的第三篇文章裏會有詳細的講解。

那麼對於新建立的協程,它的 CoroutineContext 是什麼呢?咱們已經知道一個 Job 的實例會被建立,它會幫助咱們控制協程的生命週期。而剩下的元素會從 CoroutineContext 的父類繼承,該父類多是另一個協程或者建立該協程的 CoroutineScope。函數

因爲 CoroutineScope 能夠建立協程,並且您能夠在協程內部建立更多的協程,所以內部就會隱含一個任務層級。在下面的代碼片斷中,除了經過 CoroutineScope 建立新的協程,來看看如何在協程中建立更多協程:學習

val scope = CoroutineScope(Job() + Dispatchers.Main)

val job = scope.launch {
    // 新的協程會將 CoroutineScope 做爲父級
    val result = async {
        // 經過 launch 建立的新協程會將當前協程做爲父級
    }.await()
}

層級的根一般是 CoroutineScope。圖形化該層級後以下圖所示:ui

△ 協程是以任務層級爲序執行的。

△ 協程是以任務層級爲序執行的。

父級是 CoroutineScope 或者其它協程

Job 的生命週期

一個任務能夠包含一系列狀態: 新建立 (New)、活躍 (Active)、完成中 (Completing)、已完成 (Completed)、取消中 (Cancelling) 和已取消 (Cancelled)。雖然咱們沒法直接訪問這些狀態,可是咱們能夠訪問 Job 的屬性: isActive、isCancelled 和 isCompleted。

△ Job 的生命週期

若是協程處於活躍狀態,協程運行出錯或者調用 job.cancel() 都會將當前任務置爲取消中 (Cancelling) 狀態 (isActive = false, isCancelled = true)。當全部的子協程都完成後,協程會進入已取消 (Cancelled) 狀態,此時 isCompleted = true。

解析父級 CoroutineContext

在任務層級中,每一個協程都會有一個父級對象,要麼是 CoroutineScope 或者另一個 coroutine。然而,實際上協程的父級 CoroutineContext 和父級協程的 CoroutineContext 是不同的,由於有以下的公式:

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

其中:

  • 一些元素包含默認值: Dispatchers.Default 是默認的 CoroutineDispatcher,以及 "coroutine" 做爲默認的 CoroutineName;
  • 繼承的 CoroutineContext 是 CoroutineScope 或者其父協程的 CoroutineContext;
  • 傳入協程 builder 的參數的優先級高於繼承的上下文參數,所以會覆蓋對應的參數值。
請注意: CoroutineContext 可使用 " + " 運算符進行合併。因爲 CoroutineContext 是由一組元素組成的,因此加號右側的元素會覆蓋加號左側的元素,進而組成新建立的 CoroutineContext。好比,(Dispatchers.Main, "name") + ( Dispatchers.IO) = ( Dispatchers.IO, "name")。

△ 該 CoroutineScope 所建立的每個協程,CoroutineContext 至少會包含這些元素。

△ 該 CoroutineScope 所建立的每個協程,CoroutineContext 至少會包含這些元素。

這裏的 CoroutineName 是灰色的,由於該值源於默認參數值。

那麼如今咱們明白新協程的父級 CoroutineContext 是什麼樣的了,它實際的 CoroutineContext 是:

新的 CoroutineContext = 父級 CoroutineContext + Job()

若是使用上圖中的 CoroutineScope ,咱們能夠像下面這樣建立新的協程:

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

而該協程的父級 CoroutineContext 和它實際的 CoroutineContext 是什麼樣的呢?請看下面這張圖。

△ CoroutineContext 裏的 Job 和父級上下文裏的不多是經過一個實例,由於新的協程總會拿到一個 Job 的新實例

最終的父級 CoroutineContext 會內含 Dispatchers.IO 而不是 scope 對象裏的 CoroutineDispatcher,由於它被協程的 builder 裏的參數覆蓋了。此外,注意一下父級 CoroutineContext 裏的 Job 是 scope 對象的 Job (紅色),而新的 Job 實例 (綠色) 會賦值給新的協程的 CoroutineContext。

在咱們這個系列的第三部分中,CoroutineScope 會有另一個 Job 的實現稱爲 SupervisorJob 被包含在其 CoroutineContext 中,該對象改變了 CoroutineScope 處理異常的方式。所以,由該 scope 對象建立的新協程會將一個 SupervisorJob 做爲其父級 Job。不過,當一個協程的父級是另一個協程時,父級的 Job 會仍然是 Job 類型。

如今,你們瞭解了協程的一些基本概念,在接下來的文章中,咱們將在第二篇繼續深刻探討協程的取消、第三篇探討協程的異常處理,感興趣的讀者請繼續關注咱們的更新。

相關文章
相關標籤/搜索