詳解Kotlin協(xié)程的異常處理機(jī)制
Kotlin 協(xié)程的異常處理
協(xié)程會(huì)遇到各種異常情況,比如協(xié)程被取消、協(xié)程內(nèi)部發(fā)生錯(cuò)誤、協(xié)程之間的異常傳播等。這些異常情況需要我們正確地處理,否則可能會(huì)導(dǎo)致程序崩潰、資源泄露或者邏輯錯(cuò)誤。本文將介紹 Kotlin 協(xié)程的異常處理機(jī)制,包括以下幾個(gè)方面:
- 協(xié)程的取消:介紹了協(xié)程中如何處理不同類型的異常,包括取消異常和其他異常,以及一些注意事項(xiàng)和技巧。
- 協(xié)程的取消:需要協(xié)程內(nèi)部配合,可以使用isActive或者掛起函數(shù)來響應(yīng)外部的**cancel()**方法,不要打破結(jié)構(gòu)化的父子關(guān)系。
- CancellationException異常:是一種特殊的異常,用于實(shí)現(xiàn)協(xié)程的結(jié)構(gòu)化取消,當(dāng)捕獲到這種異常時(shí),要考慮是否需要重新拋出。
- 其他異常處理手段:介紹了幾種常用的方法,如try-catch、SupervisorJob和CoroutineExceptionHandler,以及它們的使用場(chǎng)景和注意點(diǎn)。
協(xié)程的取消
協(xié)程的取消是一種協(xié)作機(jī)制,也就是說,協(xié)程需要主動(dòng)檢查自己是否被取消,并在合適的時(shí)候停止執(zhí)行。這樣做的好處是可以避免在不安全的狀態(tài)下終止協(xié)程,比如在操作共享資源或者執(zhí)行不可逆操作時(shí)。協(xié)程可以通過以下幾種方式來檢查自己是否被取消:
- 使用 isActive 屬性:這是一個(gè)布爾值,表示當(dāng)前協(xié)程是否處于活動(dòng)狀態(tài)(即未被取消)。如果為 false,則說明協(xié)程已經(jīng)被取消或者完成了。我們可以在協(xié)程中定期檢查這個(gè)屬性,并在發(fā)現(xiàn)為 false 時(shí)退出循環(huán)或者返回結(jié)果。
- 使用 掛起函數(shù):所有 kotlinx.coroutines 中的掛起函數(shù)都是 可被取消的 。它們檢查協(xié)程的取消,并在取消時(shí)拋出 CancellationException 異常。因此,我們可以在協(xié)程中調(diào)用任何掛起函數(shù)(比如 delay()、withTimeout() 等),來響應(yīng)外部的取消請(qǐng)求。如果不想處理這個(gè)異常,可以直接讓它拋出,協(xié)程會(huì)自動(dòng)結(jié)束。
下面是一個(gè)簡(jiǎn)單的例子,演示了如何使用 isActive 和 delay() 來實(shí)現(xiàn)可被取消的協(xié)程:
import kotlinx.coroutines.* fun main() = runBlocking { // 創(chuàng)建一個(gè) Job 對(duì)象 val job = launch { // 在一個(gè)循環(huán)中執(zhí)行一些工作 var i = 0 while (isActive) { // 檢查協(xié)程是否被取消 println("job: I'm working...${i++}") // 模擬耗時(shí)操作 delay(500L) } } // 等待一段時(shí)間 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..."。這是因?yàn)?delay() 函數(shù)在檢測(cè)到協(xié)程被取消時(shí),拋出了 CancellationException 異常,導(dǎo)致協(xié)程結(jié)束。如果我們不使用 delay(),而是使用 Thread.sleep(),那么協(xié)程就不會(huì)響應(yīng)取消,而是繼續(xù)執(zhí)行,直到循環(huán)結(jié)束。這是因?yàn)?Thread.sleep() 是一個(gè)阻塞函數(shù),它不會(huì)檢查協(xié)程的取消狀態(tài),也不會(huì)拋出任何異常。因此,我們應(yīng)該盡量避免在協(xié)程中使用阻塞函數(shù),而是使用掛起函數(shù)。
CancellationException 異常
CancellationException 是一種特殊的異常,它用于表示協(xié)程的正常取消。它繼承自 IllegalStateException,但是有以下幾個(gè)特點(diǎn):
- 它不會(huì)打印堆棧信息,也不會(huì)被默認(rèn)的異常處理器捕獲,因?yàn)樗淮沓绦虻腻e(cuò)誤,而是協(xié)程的協(xié)作方式。
- 它可以被 catch 語句捕獲,但是一般不需要這樣做,除非我們想在協(xié)程取消時(shí)執(zhí)行一些額外的操作,比如釋放資源、關(guān)閉連接等。如果我們只是想在協(xié)程取消時(shí)退出循環(huán)或者返回結(jié)果,那么不需要捕獲這個(gè)異常,直接讓它拋出即可。
- 它可以被 throw 語句拋出,用于主動(dòng)取消協(xié)程。我們可以在協(xié)程中調(diào)用 cancel() 方法來取消自己或者父協(xié)程,也可以直接拋出 CancellationException 來達(dá)到同樣的效果。不過,前者更加優(yōu)雅和明確,后者更加靈活和隱晦。
下面是一個(gè)例子,演示了如何在協(xié)程取消時(shí)捕獲和拋出 CancellationException 異常:
import kotlinx.coroutines.* fun main() = runBlocking { // 創(chuàng)建一個(gè) Job 對(duì)象 val job = launch { try { // 在一個(gè)循環(huán)中執(zhí)行一些工作 var i = 0 while (isActive) { // 檢查協(xié)程是否被取消 println("job: I'm working...${i++}") // 模擬耗時(shí)操作 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") } } // 等待一段時(shí)間 delay(1300L) println("main: I'm tired of waiting!") // 取消協(xié)程,并傳遞一個(gè)原因 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é)程進(jìn)入了 catch 語句,并打印了取消的原因。然后進(jìn)入了 finally 塊,并打印了一條信息。最后,在 finally 塊中拋出了一個(gè)新的 CancellationException 異常,并傳遞了一個(gè)自定義的消息。這個(gè)異常并沒有被打印或者捕獲,而是被忽略了。這是因?yàn)楫?dāng)一個(gè)協(xié)程被取消時(shí),它只關(guān)心第一個(gè) CancellationException 異常,并以它作為結(jié)束的原因。后續(xù)的任何 CancellationException 異常都會(huì)被忽略。
其他異常處理手段
除了使用 try-catch 語句來處理協(xié)程中的異常外
- 使用 SupervisorJob:這是一種特殊的 Job,它可以讓協(xié)程的子協(xié)程在發(fā)生異常時(shí)不影響父協(xié)程和其他兄弟協(xié)程的運(yùn)行。這樣,我們可以在父協(xié)程中創(chuàng)建多個(gè)子協(xié)程,分別執(zhí)行不同的任務(wù),而不用擔(dān)心其中一個(gè)任務(wù)失敗導(dǎo)致其他任務(wù)也被取消。這種方法適用于那些子協(xié)程之間沒有依賴關(guān)系,且不需要統(tǒng)一的異常處理邏輯的場(chǎng)景。
- 使用 CoroutineExceptionHandler:這是一種 CoroutineContext 的元素,它可以定義一個(gè)函數(shù),用于處理協(xié)程中未捕獲的異常我們可以在創(chuàng)建協(xié)程時(shí),將這個(gè)元素添加到協(xié)程的上下文中,或者使用 coroutineScope 或者 supervisorScope 函數(shù)來創(chuàng)建一個(gè)新的作用域,并將這個(gè)元素添加到作用域的上下文中。這樣,當(dāng)作用域內(nèi)的任何協(xié)程發(fā)生未捕獲的異常時(shí),都會(huì)調(diào)用這個(gè)函數(shù)來處理。這種方法適用于那些需要統(tǒng)一的異常處理邏輯,或者需要在異常發(fā)生時(shí)執(zhí)行一些操作(比如日志、通知等)的場(chǎng)景。
下面是一個(gè)例子,演示了如何使用 SupervisorJob 和 CoroutineExceptionHandler 來處理協(xié)程中的異常:
import kotlinx.coroutines.* fun main() = runBlocking { // 創(chuàng)建一個(gè) SupervisorJob 對(duì)象 val supervisor = SupervisorJob() // 創(chuàng)建一個(gè) CoroutineExceptionHandler 對(duì)象 val handler = CoroutineExceptionHandler { context, exception -> // 處理未捕獲的異常 println("Caught $exception in ${context[CoroutineName]}") } // 使用 supervisor 和 handler 創(chuàng)建一個(gè)新的作用域 supervisorScope { // 在作用域內(nèi)創(chuàng)建三個(gè)子協(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) // 拋出一個(gè)異常 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 的運(yùn)行,它們都正常地完成了自己的任務(wù)。這是因?yàn)槭褂昧?SupervisorJob 來創(chuàng)建作用域,使得子協(xié)程之間互不影響。同時(shí),我們也可以看到,在 child2 拋出異常后,調(diào)用了 CoroutineExceptionHandler 來處理這個(gè)異常,并打印了相關(guān)信息。這是因?yàn)槭褂昧?handler 來定義一個(gè)統(tǒng)一的異常處理函數(shù),并將它添加到 child3 的上下文中。注意,handler 并沒有添加到 child2 的上下文中,因?yàn)槿绻@樣做,那么 child2 的異常就會(huì)被捕獲并處理,而不會(huì)傳播到父協(xié)程和其他兄弟協(xié)程中。這樣就會(huì)打破 SupervisorJob 的語義,使得父協(xié)程和其他兄弟協(xié)程無法感知到 child2 的異常。因此,在使用 SupervisorJob 時(shí),我們應(yīng)該避免在子協(xié)程中使用 CoroutineExceptionHandler,而是在父協(xié)程或者其他兄弟協(xié)程中使用。
以上就是詳解Kotlin協(xié)程的異常處理機(jī)制的詳細(xì)內(nèi)容,更多關(guān)于Kotlin協(xié)程異常處理的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Android自定義View實(shí)現(xiàn)彈幕效果
這篇文章主要為大家詳細(xì)介紹了Android自定義View實(shí)現(xiàn)彈幕效果,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2019-11-11AndroidStudio不自動(dòng)添加新創(chuàng)建的文件到VCS的解決辦法
這篇文章主要介紹了AndroidStudio不自動(dòng)添加新創(chuàng)建的文件到VCS的解決辦法的相關(guān)資料,需要的朋友可以參考下2017-03-03Android中Activity過渡動(dòng)畫的實(shí)例講解
在android5.0 以上版本中,google為我們提供了幾種activity切換的過渡動(dòng)畫,目的是為了讓 activity 切換轉(zhuǎn)場(chǎng)更加美觀,下面這篇文章主要給大家介紹了關(guān)于Android中Activity過渡動(dòng)畫的相關(guān)資料,需要的朋友可以參考下2021-11-11Android自定義控件單位尺寸實(shí)現(xiàn)代碼
這篇文章主要介紹了Android自定義控件單位尺寸實(shí)現(xiàn)代碼,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-04-04OpenHarmony如何調(diào)用電話服務(wù)API撥打電話
OpenHarmony3.1版本標(biāo)準(zhǔn)系統(tǒng)增加了通話相關(guān)的聯(lián)系人應(yīng)用,來電應(yīng)用等,在系統(tǒng)服務(wù)層面電話相關(guān)功能也比較完善,這篇文章主要介紹了OpenHarmony如何調(diào)用電話服務(wù)API撥打電話2022-11-11Android Bitmap的加載優(yōu)化與Cache相關(guān)介紹
這篇文章主要介紹了Android中性能優(yōu)化之Bitmap的加載優(yōu)化與Cache相關(guān)內(nèi)容介紹,文中介紹的很詳細(xì),對(duì)大家具有一定的參考價(jià)值,需要的朋友們下面來一起看看吧。2017-02-02