原文:Coroutines: First things first
做者:Manuel Vivo
譯者:Luckykelanjava
這一系列的博客文章深刻討論了Kotlin協程中的取消和異常。爲了不浪費內存和電池壽命,協程的取消相當重要;正確的異常處理是得到良好用戶體驗的關鍵。做爲本系列其餘兩部分(第2部分:取消,第3部分:異常)的基礎,這篇博客定義一些協程的核心概念,如協程做用域、Job和協程上下文等。android
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是協程的句柄,對於您經過 launch 或 async 啓動的每一個協程,它都會返回一個Job實例,該實例惟一地標識了此協程並管理其生命週期。正如上面的代碼,您還能夠將Job對象傳遞給 CoroutineScope 來保持對此協程生命週期的控制。async
CoroutineContext 是一組定義協程行爲的元素。它由:函數
當咱們建立一個新的協程時,它的協程上下文都有什麼呢?咱們已經知道協程會返回一個新的Job 實例從而容許咱們控制它的生命週期,而其他的元素將今後協程的父元素(父協程或建立它的協程做用域)繼承[^譯者注]。
因爲協程做用域能夠建立協程,而且咱們能夠在協程中建立更多的協程,所以會造成一個隱式的任務層次結構,在下面的代碼中,除了使用協程做用域( CoroutineScope )建立一個新的協程外,還能夠看看如何在協程中建立更多的協程:spa
val scope = CoroutineScope(Job() + Dispatchers.Main)
val job = scope.launch {
// CoroutineScope做爲父級的新協程
val result = async {
// 以協程做爲父級建立的新協程
}.await()
}
複製代碼
這種層次結構的根一般爲最上層的協程做用域( CoroutineScope )。咱們能夠把該層次結構可視化以下: 線程
譯者注:這種任務的層次結構就是Koltin協程引覺得傲的結構化併發 — 父協程能夠控制和限制子線程的生命週期,子協程會繼承父協程的協程上下文 調試
Job 經歷如下一系列狀態:新建、活動、正在完成、已完成、正在取消和已取消狀態。雖然咱們沒法訪問狀態自己,但能夠訪問Job 的屬性: isActive 、isCancelled 和 isCompleted 。code
若是協程處於活動狀態,則協程失敗或調用job.cancel()
方法將使Job處於取消狀態(
isActive = false, iscancel = true
)。一旦全部的子協程完成了他們的工做,協程將進入取消狀態而且
isCompleted = true
。
在協程的任務層次結構中,每一個協程都有一個父級,能夠是一個協程做用域,也能夠是另外一個協程。然而,協程繼承的父級協程上下文可能不一樣於父級自己的上下文,由於協程上下文是基於這個公式計算的cdn
父級上下文 = 默認值 + 繼承的上下文 + 參數
Dispatchers.Default
是協程調度器( CoroutineDispatcher )的默認值,「coroutine」是 CoroutineName 的默認值。注意: CoroutineContext 可使用+運算符組合,因爲 CoroutineContext 是一組元素,所以會將加號右邊的元素覆蓋左側相同的元素以建立一個新的 CoroutineContext ,如 (Dispatchers.Main, 「name」) + (Dispatchers.IO) = (Dispatchers.IO, 「name」)
譯者注:根據CoroutineContext的源碼,CoroutineContext在內部使用鍵值對來維護元素,比較神奇的操做是,這些鍵值對的鍵對應着值類型的伴生對象,值爲這些類型的實例,因此同一類型的元素在同一個CoroutineContext中惟一。
如今咱們知道了一個新的協程的父級協程上下文是什麼,那麼它實際的協程上下文將是:
新協程的上下文 = 父級CoroutineContext + Job()
若是使用上圖的scope建立一個新協程,就像這樣:
val job = scope.launch(Dispatchers.IO){
//新協程
}
複製代碼
那麼這個新協程的協程上下文和父級協程上下文將是什麼呢?請參見下圖的答案!
咱們看到父級上下文中有 Dispatchers.IO
,而不是聲明
scope 時咱們傳入的
Dispatchers.Main
,由於它被協程構建器
launch 的參數覆蓋了。另外,請注意父級上下文中的Job實例是scope對象的Job,而新協程的實際Job實例是被從新分配的(綠色)。