Kotlin協(xié)程異常機(jī)制

  • 協(xié)程的3種作用域以及異常的傳播方式
  • 協(xié)程異常的兩種捕獲方式及對(duì)比
  • 協(xié)程異常的優(yōu)雅封裝

作用域以及異常的傳播方式

協(xié)程作用域分為頂級(jí)作用域,協(xié)同作用域與主從作用域,分別對(duì)應(yīng)GlobalScope,coroutineScope,supervisorScope。


協(xié)程作用域分類.png

協(xié)程作用域范圍.png

說明:

C2-1發(fā)生異常的時(shí)候,C2-1->C2->C2-2->C2->C1->C3(包括里面的子協(xié)程)->C4

C3-1-1發(fā)生異常的時(shí)候,C3-1-1->C3-1-1-1,其他不受影響

C3-1-1-1發(fā)生異常的時(shí)候,C3-1-1-1->C3-1-1,其他不受影響

// C1和C2沒有關(guān)系
GlobalScope.launch { //協(xié)程C1
    GlobalScope.launch {//協(xié)程C2
        //...
    }
}
// C2和C3是C1的子協(xié)程,C2和C3異常會(huì)取消C1
GlobalScope.launch { //協(xié)程C1
    coroutineScoope {
         launch{}//協(xié)程C2
         launch{}//協(xié)程C3
    }
}
// C2和C3是C1的子協(xié)程,C2和C3異常不會(huì)取消C1
GlobalScope.launch { //協(xié)程C1
    supervisorScope {
         launch{}//協(xié)程C2
         launch{}//協(xié)程C3
    }
}

如何捕獲異常

傳播的異常可以通過CoroutineExceptionHandler來捕獲
如果協(xié)程本身不使用try-catch子句自行處理異常,則不會(huì)重新拋出該異常,因此無法通過外部try-catch子句進(jìn)行處理。

異常會(huì)在“Job層次結(jié)構(gòu)中傳播”,可以由已設(shè)置的CoroutineExceptionHandler處理。如果未設(shè)置,則調(diào)用該線程的未捕獲異常處理程序。

CoroutineExceptionHandler

Try Catch與CoroutineExceptionHandler對(duì)比
協(xié)程支持兩種異常處理機(jī)制,那么我們應(yīng)該選擇哪種呢?

CoroutineExceptionHandler的官方文檔提供了一些很好的答案:

“ CoroutineExceptionHandler是用于全局“全部捕獲”行為的最后手段。您無法從CoroutineExceptionHandler中的異常中恢復(fù)。當(dāng)調(diào)用處理程序時(shí),協(xié)程已經(jīng)完成,并帶有相應(yīng)的異常。通常,處理程序用于記錄異常,顯示某種錯(cuò)誤消息,終止和/或重新啟動(dòng)應(yīng)用程序。

如果需要在代碼的特定部分處理異常,建議在協(xié)程內(nèi)部的相應(yīng)代碼周圍使用try / catch。這樣,您可以防止協(xié)程異常完成(現(xiàn)在已捕獲異常),重試該操作和/或采取其他任意操作:”

launch{} vs async{}

fun main() {

    val topLevelScope = CoroutineScope(SupervisorJob())

    topLevelScope.async {
        throw RuntimeException("RuntimeException in async coroutine")
    }

    Thread.sleep(100)
}

// 沒有輸出
fun main() {

    val coroutineExceptionHandler = CoroutineExceptionHandler { coroutineContext, exception ->
        println("Handle $exception in CoroutineExceptionHandler")
    }

    val topLevelScope = CoroutineScope(SupervisorJob() + coroutineExceptionHandler)
    topLevelScope.launch {
        async {
            throw RuntimeException("RuntimeException in async coroutine")
        }
    }
    Thread.sleep(100)
}

// 輸出
// Handle java.lang.RuntimeException: RuntimeException in async coroutine in CoroutineExceptionHandler

launch和async協(xié)程中未捕獲的異常會(huì)立即在作業(yè)層次結(jié)構(gòu)中傳播。
但是,如果頂層Coroutine是從launch啟動(dòng)的,則異常將由CoroutineExceptionHandler處理或傳遞給線程的未捕獲異常處理程序。

如果頂級(jí)協(xié)程以async方式啟動(dòng),則異常封裝在Deferred返回類型中,并在調(diào)用.await()時(shí)重新拋出。

coroutineScope異常處理特性

范圍函數(shù)coroutineScope {}重新拋出其失敗的子協(xié)程的異常,而不是將其傳播到Job層次結(jié)構(gòu)中,這使我們能夠使用try-catch處理失敗的協(xié)程的異常。

supervisorScope異常處理特性

范圍函數(shù)supervisorScope {}在Job層次結(jié)構(gòu)中添加了一個(gè)新的獨(dú)立子范圍,并將SupervisorJob作為這個(gè)scope的'job'。

這個(gè)新作用域不會(huì)在“Job層次結(jié)構(gòu)”中傳播其異常,因此它必須自行處理其異常。直接從supervisorScope啟動(dòng)的協(xié)程是頂級(jí)協(xié)程。

頂級(jí)協(xié)程與子協(xié)程在使用launch()或async()啟動(dòng)時(shí)的行為有所不同,此外,還可以在它們中安裝CoroutineExceptionHandlers。

協(xié)程異常的優(yōu)雅封裝

我們可以對(duì)CoroutineExceptionHandler進(jìn)行封裝,利用kotlin擴(kuò)展函數(shù),實(shí)現(xiàn)類似RxJava的調(diào)用效果。最后調(diào)用效果如下:

fun fetch() {
        viewModelScope.rxLaunch<String> {
            onRequest = {
                //網(wǎng)絡(luò)請(qǐng)求
                resposity.getData()
            }
            onSuccess = {
                //成功回調(diào)
            }
            onError = {
                //失敗回調(diào)
            }
        }
    }

主要利用kotlin擴(kuò)展函數(shù)及DSL語法,封裝協(xié)程異常處理,達(dá)到類似RxJava調(diào)用的效果

fun <T> CoroutineScope.rxLaunch(init: CoroutineBuilder<T>.() -> Unit) {
    val result = CoroutineBuilder<T>().apply(init)
    val coroutineExceptionHandler = CoroutineExceptionHandler { _, exception ->
        result.onError?.invoke(exception)
    }
    launch(coroutineExceptionHandler) {
        val res: T? = result.onRequest?.invoke()
        res?.let {
            result.onSuccess?.invoke(it)
        }
    }
}

class CoroutineBuilder<T> {
    var onRequest: (suspend () -> T)? = null
    var onSuccess: ((T) -> Unit)? = null
    var onError: ((Throwable) -> Unit)? = null
}
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容