詳解Kotlin協(xié)程的異常處理機制
Kotlin 協(xié)程的異常處理
協(xié)程會遇到各種異常情況,比如協(xié)程被取消、協(xié)程內(nèi)部發(fā)生錯誤、協(xié)程之間的異常傳播等。這些異常情況需要我們正確地處理,否則可能會導(dǎo)致程序崩潰、資源泄露或者邏輯錯誤。本文將介紹 Kotlin 協(xié)程的異常處理機制,包括以下幾個方面:
- 協(xié)程的取消:介紹了協(xié)程中如何處理不同類型的異常,包括取消異常和其他異常,以及一些注意事項和技巧。
- 協(xié)程的取消:需要協(xié)程內(nèi)部配合,可以使用isActive或者掛起函數(shù)來響應(yīng)外部的**cancel()**方法,不要打破結(jié)構(gòu)化的父子關(guān)系。
- CancellationException異常:是一種特殊的異常,用于實現(xiàn)協(xié)程的結(jié)構(gòu)化取消,當(dāng)捕獲到這種異常時,要考慮是否需要重新拋出。
- 其他異常處理手段:介紹了幾種常用的方法,如try-catch、SupervisorJob和CoroutineExceptionHandler,以及它們的使用場景和注意點。
協(xié)程的取消
協(xié)程的取消是一種協(xié)作機制,也就是說,協(xié)程需要主動檢查自己是否被取消,并在合適的時候停止執(zhí)行。這樣做的好處是可以避免在不安全的狀態(tài)下終止協(xié)程,比如在操作共享資源或者執(zhí)行不可逆操作時。協(xié)程可以通過以下幾種方式來檢查自己是否被取消:
- 使用 isActive 屬性:這是一個布爾值,表示當(dāng)前協(xié)程是否處于活動狀態(tài)(即未被取消)。如果為 false,則說明協(xié)程已經(jīng)被取消或者完成了。我們可以在協(xié)程中定期檢查這個屬性,并在發(fā)現(xiàn)為 false 時退出循環(huán)或者返回結(jié)果。
- 使用 掛起函數(shù):所有 kotlinx.coroutines 中的掛起函數(shù)都是 可被取消的 。它們檢查協(xié)程的取消,并在取消時拋出 CancellationException 異常。因此,我們可以在協(xié)程中調(diào)用任何掛起函數(shù)(比如 delay()、withTimeout() 等),來響應(yīng)外部的取消請求。如果不想處理這個異常,可以直接讓它拋出,協(xié)程會自動結(jié)束。
下面是一個簡單的例子,演示了如何使用 isActive 和 delay() 來實現(xiàn)可被取消的協(xié)程:
import kotlinx.coroutines.*
fun main() = runBlocking {
// 創(chuàng)建一個 Job 對象
val job = launch {
// 在一個循環(huán)中執(zhí)行一些工作
var i = 0
while (isActive) { // 檢查協(xié)程是否被取消
println("job: I'm working...${i++}")
// 模擬耗時操作
delay(500L)
}
}
// 等待一段時間
delay(1300L)
println("main: I'm tired of waiting!")
// 取消協(xié)程
job.cancel()
println("main: Now I can quit.")
}輸出結(jié)果:
job: I'm working...0
job: I'm working...1
job: I'm working...2
main: I'm tired of waiting!
main: Now I can quit.
從輸出結(jié)果可以看出,在調(diào)用 job.cancel() 后,循環(huán)就停止了,并沒有繼續(xù)打印 "job: I'm working..."。這是因為 delay() 函數(shù)在檢測到協(xié)程被取消時,拋出了 CancellationException 異常,導(dǎo)致協(xié)程結(jié)束。如果我們不使用 delay(),而是使用 Thread.sleep(),那么協(xié)程就不會響應(yīng)取消,而是繼續(xù)執(zhí)行,直到循環(huán)結(jié)束。這是因為 Thread.sleep() 是一個阻塞函數(shù),它不會檢查協(xié)程的取消狀態(tài),也不會拋出任何異常。因此,我們應(yīng)該盡量避免在協(xié)程中使用阻塞函數(shù),而是使用掛起函數(shù)。
CancellationException 異常
CancellationException 是一種特殊的異常,它用于表示協(xié)程的正常取消。它繼承自 IllegalStateException,但是有以下幾個特點:
- 它不會打印堆棧信息,也不會被默認(rèn)的異常處理器捕獲,因為它不代表程序的錯誤,而是協(xié)程的協(xié)作方式。
- 它可以被 catch 語句捕獲,但是一般不需要這樣做,除非我們想在協(xié)程取消時執(zhí)行一些額外的操作,比如釋放資源、關(guān)閉連接等。如果我們只是想在協(xié)程取消時退出循環(huán)或者返回結(jié)果,那么不需要捕獲這個異常,直接讓它拋出即可。
- 它可以被 throw 語句拋出,用于主動取消協(xié)程。我們可以在協(xié)程中調(diào)用 cancel() 方法來取消自己或者父協(xié)程,也可以直接拋出 CancellationException 來達到同樣的效果。不過,前者更加優(yōu)雅和明確,后者更加靈活和隱晦。
下面是一個例子,演示了如何在協(xié)程取消時捕獲和拋出 CancellationException 異常:
import kotlinx.coroutines.*
fun main() = runBlocking {
// 創(chuàng)建一個 Job 對象
val job = launch {
try {
// 在一個循環(huán)中執(zhí)行一些工作
var i = 0
while (isActive) { // 檢查協(xié)程是否被取消
println("job: I'm working...${i++}")
// 模擬耗時操作
delay(500L)
}
} catch (e: CancellationException) {
// 捕獲取消異常
println("job: I'm cancelled, reason: ${e.message}")
} finally {
// 在 finally 塊中執(zhí)行一些操作
println("job: I'm in the finally block")
// 拋出取消異常
throw CancellationException("I don't want to finish normally")
}
}
// 等待一段時間
delay(1300L)
println("main: I'm tired of waiting!")
// 取消協(xié)程,并傳遞一個原因
job.cancel(CancellationException("Too slow"))
println("main: Now I can quit.")
}輸出結(jié)果:
job: I'm working...0
job: I'm working...1
job: I'm working...2
main: I'm tired of waiting!
job: I'm cancelled, reason: Too slow
job: I'm in the finally block
main: Now I can quit.
從輸出結(jié)果可以看出,在調(diào)用 job.cancel() 后,協(xié)程進入了 catch 語句,并打印了取消的原因。然后進入了 finally 塊,并打印了一條信息。最后,在 finally 塊中拋出了一個新的 CancellationException 異常,并傳遞了一個自定義的消息。這個異常并沒有被打印或者捕獲,而是被忽略了。這是因為當(dāng)一個協(xié)程被取消時,它只關(guān)心第一個 CancellationException 異常,并以它作為結(jié)束的原因。后續(xù)的任何 CancellationException 異常都會被忽略。
其他異常處理手段
除了使用 try-catch 語句來處理協(xié)程中的異常外
- 使用 SupervisorJob:這是一種特殊的 Job,它可以讓協(xié)程的子協(xié)程在發(fā)生異常時不影響父協(xié)程和其他兄弟協(xié)程的運行。這樣,我們可以在父協(xié)程中創(chuàng)建多個子協(xié)程,分別執(zhí)行不同的任務(wù),而不用擔(dān)心其中一個任務(wù)失敗導(dǎo)致其他任務(wù)也被取消。這種方法適用于那些子協(xié)程之間沒有依賴關(guān)系,且不需要統(tǒng)一的異常處理邏輯的場景。
- 使用 CoroutineExceptionHandler:這是一種 CoroutineContext 的元素,它可以定義一個函數(shù),用于處理協(xié)程中未捕獲的異常我們可以在創(chuàng)建協(xié)程時,將這個元素添加到協(xié)程的上下文中,或者使用 coroutineScope 或者 supervisorScope 函數(shù)來創(chuàng)建一個新的作用域,并將這個元素添加到作用域的上下文中。這樣,當(dāng)作用域內(nèi)的任何協(xié)程發(fā)生未捕獲的異常時,都會調(diào)用這個函數(shù)來處理。這種方法適用于那些需要統(tǒng)一的異常處理邏輯,或者需要在異常發(fā)生時執(zhí)行一些操作(比如日志、通知等)的場景。
下面是一個例子,演示了如何使用 SupervisorJob 和 CoroutineExceptionHandler 來處理協(xié)程中的異常:
import kotlinx.coroutines.*
fun main() = runBlocking {
// 創(chuàng)建一個 SupervisorJob 對象
val supervisor = SupervisorJob()
// 創(chuàng)建一個 CoroutineExceptionHandler 對象
val handler = CoroutineExceptionHandler { context, exception ->
// 處理未捕獲的異常
println("Caught $exception in ${context[CoroutineName]}")
}
// 使用 supervisor 和 handler 創(chuàng)建一個新的作用域
supervisorScope {
// 在作用域內(nèi)創(chuàng)建三個子協(xié)程
val child1 = launch(supervisor + CoroutineName("child1")) {
println("child1: I'm working...")
delay(500L)
println("child1: I'm done.")
}
val child2 = launch(supervisor + CoroutineName("child2")) {
println("child2: I'm working...")
delay(1000L)
// 拋出一個異常
throw ArithmeticException("Oops!")
}
val child3 = launch(supervisor + handler + CoroutineName("child3")) {
println("child3: I'm working...")
delay(1500L)
println("child3: I'm done.")
}
}
// 等待作用域結(jié)束
println("main: The scope is over.")
}輸出結(jié)果:
child1: I'm working...
child2: I'm working...
child3: I'm working...
child1: I'm done.
Caught java.lang.ArithmeticException: Oops! in child2
child3: I'm done.
main: The scope is over.
從輸出結(jié)果可以看出,在 child2 拋出異常后,并沒有影響 child1 和 child3 的運行,它們都正常地完成了自己的任務(wù)。這是因為使用了 SupervisorJob 來創(chuàng)建作用域,使得子協(xié)程之間互不影響。同時,我們也可以看到,在 child2 拋出異常后,調(diào)用了 CoroutineExceptionHandler 來處理這個異常,并打印了相關(guān)信息。這是因為使用了 handler 來定義一個統(tǒng)一的異常處理函數(shù),并將它添加到 child3 的上下文中。注意,handler 并沒有添加到 child2 的上下文中,因為如果這樣做,那么 child2 的異常就會被捕獲并處理,而不會傳播到父協(xié)程和其他兄弟協(xié)程中。這樣就會打破 SupervisorJob 的語義,使得父協(xié)程和其他兄弟協(xié)程無法感知到 child2 的異常。因此,在使用 SupervisorJob 時,我們應(yīng)該避免在子協(xié)程中使用 CoroutineExceptionHandler,而是在父協(xié)程或者其他兄弟協(xié)程中使用。
以上就是詳解Kotlin協(xié)程的異常處理機制的詳細內(nèi)容,更多關(guān)于Kotlin協(xié)程異常處理的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Android中阻止AlertDialog關(guān)閉實例代碼
這篇文章主要介紹了Android阻止AlertDialog關(guān)閉實例代碼的相關(guān)資料,需要的朋友可以參考下2016-03-03
Android音頻編輯之音頻轉(zhuǎn)換PCM與WAV
這篇文章主要為大家詳細介紹了Android音頻編輯之音頻轉(zhuǎn)換PCM與WAV,具有一定的參考價值,感興趣的小伙伴們可以參考一下2019-01-01
android: targetSdkVersion升級中Only fullscreen activities can r
這篇文章主要給大家介紹了關(guān)于Android target SDK和build tool版本升級中遇到Only fullscreen activities can request orientation問題的解決方法,文中通過示例代碼介紹的非常詳細,需要的朋友可以參考下2018-09-09
Android convinientbanner頂部廣告輪播控件使用詳解
這篇文章主要為大家詳細介紹了Android convinientbanner頂部廣告輪播控件,具有一定的參考價值,感興趣的小伙伴們可以參考一下2017-01-01
Android自定義view實現(xiàn)電影票在線選座功能
這篇文章主要為大家詳細介紹了Android自定義view實現(xiàn)選座功能,文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下2016-11-11
Android MessageQueue消息隊列主要作用詳解
Android 消息機制主要指的是 Handler 的運行機制及其所依賴的 MessageQueue 和 Looper 的工作過程,Handler、MessageQueue、Looper組成一個相互聯(lián)系的整體。本文先從 MessageQueue 的源碼來說明其實現(xiàn)原理2023-02-02

