協(xié)程中的取消和異常 | 核心概念介紹
在之前的文章里,我們?yōu)楦魑婚_發(fā)者分享了在 Android 中使用協(xié)程的一些基礎(chǔ)知識(shí),包括在 Android 協(xié)程的背景介紹、上手指南和代碼實(shí)戰(zhàn)。本次系列文章 "協(xié)程中的取消和異常" 也是 Android 協(xié)程相關(guān)的內(nèi)容,我們將與大家深入探討協(xié)程中關(guān)于取消操作和異常處理的知識(shí)點(diǎn)和技巧。
當(dāng)我們需要避免多余的處理來減少內(nèi)存浪費(fèi)并節(jié)省電量時(shí),取消操作就顯得尤為重要;而妥善的異常處理也是提高用戶體驗(yàn)的關(guān)鍵。本篇是另外兩篇文章的基礎(chǔ) (第二篇和第三篇將為大家分別詳解協(xié)程取消操作和異常處理), 所以有必要先講解一些協(xié)程的核心概念,比如 CoroutineScope (協(xié)程作用域)、Job (任務(wù)) 和 CoroutineContext (協(xié)程上下文),這樣我們才能夠進(jìn)行更深入的學(xué)習(xí)。
CoroutineScope
CoroutineScope 會(huì)追蹤每一個(gè)您通過 launch 或者 async 創(chuàng)建的協(xié)程 (這兩個(gè)是 CoroutineScope 的擴(kuò)展函數(shù))。任何時(shí)候都可通過調(diào)用 scope.cancel() 來取消正在進(jìn)行的工作 (正在運(yùn)行的協(xié)程)。
- CoroutineScope:https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-coroutine-scope/
- launch:https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/launch.html
- async:https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/async.html
當(dāng)您希望在應(yīng)用程序的某一個(gè)層次開啟或者控制協(xié)程的生命周期時(shí),您需要?jiǎng)?chuàng)建一個(gè) CoroutineScope。對(duì)于一些平臺(tái),比如 Android,已經(jīng)有 KTX 這樣的庫在一些類的生命周期里提供了 CoroutineScope,比如 viewModelScope 和 lifecycleScope。
- viewModelScope:https://developer.android.google.cn/reference/kotlin/androidx/lifecycle/package-summary#(androidx.lifecycle.ViewModel).viewModelScope:kotlinx.coroutines.CoroutineScope
- lifecycleScope:https://developer.android.google.cn/reference/kotlin/androidx/lifecycle/package-summary#lifecyclescope
當(dāng)創(chuàng)建 CoroutineScope 的時(shí)候,它會(huì)將 CoroutineContext 作為構(gòu)造函數(shù)的參數(shù)。您可以通過下面代碼創(chuàng)建一個(gè)新的 scope 和協(xié)程:
- //Job 和 Dispatcher 已經(jīng)被集成到了 CoroutineContext
- //后面我們?cè)敿?xì)介紹
- val scope = CoroutineScope(Job() + Dispatchers.Main)
- val job = scope.launch {
- //新的協(xié)程
- }
Job
Job 用于處理協(xié)程。對(duì)于每一個(gè)您所創(chuàng)建的協(xié)程 (通過 launch 或者 async),它會(huì)返回一個(gè) Job 實(shí)例,該實(shí)例是協(xié)程的唯一標(biāo)識(shí),并且負(fù)責(zé)管理協(xié)程的生命周期。正如我們上面看到的,您可以將 Job 實(shí)例傳遞給 CoroutineScope 來控制其生命周期。
Job:
https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-job/index.html
CoroutineContext
CoroutineContext 是一組用于定義協(xié)程行為的元素。它由如下幾項(xiàng)構(gòu)成:
- Job:控制協(xié)程的生命周期;
- CoroutineDispatcher:向合適的線程分發(fā)任務(wù);
- CoroutineName:協(xié)程的名稱,調(diào)試的時(shí)候很有用;
- CoroutineExceptionHandler:處理未被捕捉的異常,在未來的第三篇文章里會(huì)有詳細(xì)的講解。
CoroutineContex:
thttps://kotlinlang.org/api/latest/jvm/stdlib/kotlin.coroutines/-coroutine-context/index.html
那么對(duì)于新創(chuàng)建的協(xié)程,它的 CoroutineContext 是什么呢?我們已經(jīng)知道一個(gè) Job 的實(shí)例會(huì)被創(chuàng)建,它會(huì)幫助我們控制協(xié)程的生命周期。而剩下的元素會(huì)從 CoroutineContext 的父類繼承,該父類可能是另外一個(gè)協(xié)程或者創(chuàng)建該協(xié)程的 CoroutineScope。
由于 CoroutineScope 可以創(chuàng)建協(xié)程,而且您可以在協(xié)程內(nèi)部創(chuàng)建更多的協(xié)程,因此內(nèi)部就會(huì)隱含一個(gè)任務(wù)層級(jí)。在下面的代碼片段中,除了通過 CoroutineScope 創(chuàng)建新的協(xié)程,來看看如何在協(xié)程中創(chuàng)建更多協(xié)程:
- val scope = CoroutineScope(Job() + Dispatchers.Main)
- val job = scope.launch {
- // 新的協(xié)程會(huì)將 CoroutineScope 作為父級(jí)
- val result = async {
- // 通過 launch 創(chuàng)建的新協(xié)程會(huì)將當(dāng)前協(xié)程作為父級(jí)
- }.await()
- }
層級(jí)的根通常是 CoroutineScope。圖形化該層級(jí)后如下圖所示:
△ 協(xié)程是以任務(wù)層級(jí)為序執(zhí)行的。
父級(jí)是 CoroutineScope 或者其它協(xié)程
Job 的生命周期
一個(gè)任務(wù)可以包含一系列狀態(tài): 新創(chuàng)建 (New)、活躍 (Active)、完成中 (Completing)、已完成 (Completed)、取消中 (Cancelling) 和已取消 (Cancelled)。雖然我們無法直接訪問這些狀態(tài),但是我們可以訪問 Job 的屬性: isActive、isCancelled 和 isCompleted。
△ Job 的生命周期
如果協(xié)程處于活躍狀態(tài),協(xié)程運(yùn)行出錯(cuò)或者調(diào)用 job.cancel() 都會(huì)將當(dāng)前任務(wù)置為取消中 (Cancelling) 狀態(tài) (isActive = false, isCancelled = true)。當(dāng)所有的子協(xié)程都完成后,協(xié)程會(huì)進(jìn)入已取消 (Cancelled) 狀態(tài),此時(shí) isCompleted = true。
解析父級(jí) CoroutineContext
在任務(wù)層級(jí)中,每個(gè)協(xié)程都會(huì)有一個(gè)父級(jí)對(duì)象,要么是 CoroutineScope 或者另外一個(gè) coroutine。然而,實(shí)際上協(xié)程的父級(jí) CoroutineContext 和父級(jí)協(xié)程的 CoroutineContext 是不一樣的,因?yàn)橛腥缦碌墓剑?/p>
父級(jí)上下文 = 默認(rèn)值 + 繼承的 CoroutineContext + 參數(shù)
其中:
- 一些元素包含默認(rèn)值: Dispatchers.Default 是默認(rèn)的 CoroutineDispatcher,以及 "coroutine" 作為默認(rèn)的 CoroutineName;
- 繼承的 CoroutineContext 是 CoroutineScope 或者其父協(xié)程的 CoroutineContext;
- 傳入?yún)f(xié)程 builder 的參數(shù)的優(yōu)先級(jí)高于繼承的上下文參數(shù),因此會(huì)覆蓋對(duì)應(yīng)的參數(shù)值。
請(qǐng)注意: CoroutineContext 可以使用 " + " 運(yùn)算符進(jìn)行合并。由于 CoroutineContext 是由一組元素組成的,所以加號(hào)右側(cè)的元素會(huì)覆蓋加號(hào)左側(cè)的元素,進(jìn)而組成新創(chuàng)建的 CoroutineContext。比如,(Dispatchers.Main, "name") + (Dispatchers.IO) = (Dispatchers.IO, "name")。
Dispatchers.IO:http://dispatchers.io/
該 CoroutineScope 所創(chuàng)建的每一個(gè)協(xié)程,CoroutineContext 至少會(huì)包含這些元素。這里的 CoroutineName 是灰色的,因?yàn)樵撝翟从谀J(rèn)參數(shù)值。那么現(xiàn)在我們明白新協(xié)程的父級(jí) CoroutineContext 是什么樣的了,它實(shí)際的 CoroutineContext 是:
新的 CoroutineContext = 父級(jí) CoroutineContext + Job()
如果使用上圖中的 CoroutineScope ,我們可以像下面這樣創(chuàng)建新的協(xié)程:
- val job = scope.launch(Dispatchers.IO) {
- //新協(xié)程
- }
而該協(xié)程的父級(jí) CoroutineContext 和它實(shí)際的 CoroutineContext 是什么樣的呢?請(qǐng)看下面這張圖。
CoroutineContext 里的 Job 和父級(jí)上下文里的不可能是通過一個(gè)實(shí)例,因?yàn)樾碌膮f(xié)程總會(huì)拿到一個(gè) Job 的新實(shí)例。
最終的父級(jí) CoroutineContext 會(huì)內(nèi)含 Dispatchers.IO 而不是 scope 對(duì)象里的 CoroutineDispatcher,因?yàn)樗粎f(xié)程的 builder 里的參數(shù)覆蓋了。此外,注意一下父級(jí) CoroutineContext 里的 Job 是 scope 對(duì)象的 Job (紅色),而新的 Job 實(shí)例 (綠色) 會(huì)賦值給新的協(xié)程的 CoroutineContext。
在我們這個(gè)系列的第三部分中,CoroutineScope 會(huì)有另外一個(gè) Job 的實(shí)現(xiàn)稱為 SupervisorJob 被包含在其 CoroutineContext 中,該對(duì)象改變了 CoroutineScope 處理異常的方式。因此,由該 scope 對(duì)象創(chuàng)建的新協(xié)程會(huì)將一個(gè) SupervisorJob 作為其父級(jí) Job。不過,當(dāng)一個(gè)協(xié)程的父級(jí)是另外一個(gè)協(xié)程時(shí),父級(jí)的 Job 會(huì)仍然是 Job 類型。
現(xiàn)在,大家了解了協(xié)程的一些基本概念,在接下來的文章中,我們將在第二篇繼續(xù)深入探討協(xié)程的取消、第三篇探討協(xié)程的異常處理,感興趣的讀者請(qǐng)繼續(xù)關(guān)注我們的更新。
【本文是51CTO專欄機(jī)構(gòu)“谷歌開發(fā)者”的原創(chuàng)稿件,轉(zhuǎn)載請(qǐng)聯(lián)系原作者(微信公眾號(hào):Google_Developers)】

































