協(xié)程中的取消和異常 (異常處理詳解)

某個協(xié)程運行出現(xiàn)異常怎么辦旗扑?

當某個協(xié)程運行出現(xiàn)異常的時候,那么會有以下幾個操作:

  • 取消自己的子級
  • 取消自己
  • 將異常傳播給父級
    最新異常會到達CoroutineScope 的根本袍暴,并且CoroutineScope 啟動的所有協(xié)程都會被取消

當協(xié)程出現(xiàn)異常只取消自己,不影響其他協(xié)程

當協(xié)程運行出現(xiàn)異常的時候,我們希望不影響其他協(xié)程拄氯,只取消自己即可火诸,我們可以使用SupervisorJob 和supervisorScope

  • 例子1使用普通的Job
import kotlinx.coroutines.*

suspend fun main() {
// Scope 控制我的應用中某一層級的協(xié)程
    val scopeParent = CoroutineScope(Job())
    val scopeChild = CoroutineScope(Job())
    val scopeSChild = CoroutineScope(SupervisorJob())
    scopeParent.launch {
        //child1
        scopeChild.launch {
            1 / 0
        }
        //child2
        scopeChild.launch {
            delay(1000)
            println("你好")
        }
    }

    Thread.sleep(2000)
}

Exception in thread "DefaultDispatcher-worker-2" java.lang.ArithmeticException: / by zero
    at CoroutineKt$main$2$1.invokeSuspend(coroutine.kt:17)
    at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
    at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:56)
    at kotlinx.coroutines.scheduling.CoroutineScheduler.runSafely(CoroutineScheduler.kt:571)
    at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.executeTask(CoroutineScheduler.kt:738)
    at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.runWorker(CoroutineScheduler.kt:678)
    at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.run(CoroutineScheduler.kt:665)

使用普通的Job的時候锦针,child1出現(xiàn)異常的時候child2也被取消,所以child2沒有執(zhí)行

  • 例子2使用SupervisorJob
import kotlinx.coroutines.*

suspend fun main() {
// Scope 控制我的應用中某一層級的協(xié)程
    val scopeParent = CoroutineScope(Job())
    val scopeChild = CoroutineScope(Job())
    val scopeSChild = CoroutineScope(SupervisorJob())
    scopeParent.launch {
        //child1
        scopeSChild.launch {
            1 / 0
        }
        //child2
        scopeSChild.launch {
            delay(1000)
            println("你好")
        }
    }

    Thread.sleep(2000)
}
Exception in thread "DefaultDispatcher-worker-1" java.lang.ArithmeticException: / by zero
    at CoroutineKt$main$2$1.invokeSuspend(coroutine.kt:11)
    at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
    at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:56)
    at kotlinx.coroutines.scheduling.CoroutineScheduler.runSafely(CoroutineScheduler.kt:571)
    at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.executeTask(CoroutineScheduler.kt:738)
    at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.runWorker(CoroutineScheduler.kt:678)
    at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.run(CoroutineScheduler.kt:665)
你好

使用普通的SupervisorJob的時候置蜀,child1出現(xiàn)異常的時候child2繼續(xù)執(zhí)行

  • 例子3使用supervisorScope
import kotlinx.coroutines.*

suspend fun main() {
// Scope 控制我的應用中某一層級的協(xié)程
    val scopeParent = CoroutineScope(Job())
    val scopeChild = CoroutineScope(Job())
    val scopeSChild = CoroutineScope(SupervisorJob())
    scopeParent.launch {
        //child1
        supervisorScope {
            launch {
                1 / 0
            }
        }
        //child2
        supervisorScope {
            launch {
                delay(1000)
                println("你好")
            }
        }

    }

    Thread.sleep(2000)
}

Exception in thread "DefaultDispatcher-worker-2" java.lang.ArithmeticException: / by zero
    at CoroutineKt$main$2$1$1.invokeSuspend(coroutine.kt:12)
    at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
    at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:56)
    at kotlinx.coroutines.scheduling.CoroutineScheduler.runSafely(CoroutineScheduler.kt:571)
    at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.executeTask(CoroutineScheduler.kt:738)
    at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.runWorker(CoroutineScheduler.kt:678)
    at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.run(CoroutineScheduler.kt:665)
你好

使用supervisorScope 也一樣奈搜,child1出現(xiàn)異常,不會影響到child2

  • 例子3使用try catch
import kotlinx.coroutines.*
import java.lang.Exception

suspend fun main() {
// Scope 控制我的應用中某一層級的協(xié)程
    val scopeParent = CoroutineScope(Job())
    val scopeChild = CoroutineScope(Job())
    val scopeSChild = CoroutineScope(SupervisorJob())
    scopeParent.launch {
        //child1
        scopeChild.launch {
            try {
                1 / 0
            } catch (e: Exception) {
                println(e)
            }
        }
        //child2
        scopeChild.launch {
            delay(1000)
            println("你好")
        }

    }

    Thread.sleep(2000)
}
java.lang.ArithmeticException: / by zero
你好

使用try catch把child1的異常捕捉之后盯荤,也不會影響到child2

處理異常

我們使用launch啟動的協(xié)程馋吗,我們使用try catch可以捕捉異常


scope.launch {
    try {
        codeThatCanThrowExceptions()
    } catch(e: Exception) {
        // 處理異常
    }
}

如果我們使用了async,用于返回結(jié)果的協(xié)程秋秤,異常會在我們調(diào)用.await()方法的時候拋出


supervisorScope {
    val deferred = async {
        codeThatCanThrowExceptions()
    }
 
   try {
       deferred.await()
   } catch(e: Exception) {
       // 處理 async 中拋出的異常
   }
}

所以我們在使用async的時候耗美,不要將try catch放在async中,而是放在.await()中航缀。

CoroutineExceptionHandler

我們知道CoroutineExceptionHandler是CoroutineContext 上下文的一個可選的元素商架,當我們沒用使用try catch捕捉異常的時候,我們可以使用CoroutineExceptionHandler來捕捉異常芥玉。


val handler = CoroutineExceptionHandler {
   context, exception -> println("Caught $exception")
}

使用CoroutineExceptionHandler捕捉異常的前提是通過launch啟動的協(xié)程
例如1:


import kotlinx.coroutines.*
import java.lang.Exception

suspend fun main() {
// Scope 控制我的應用中某一層級的協(xié)程
    val scopeParent = CoroutineScope(Job())
    val scopeChild = CoroutineScope(Job())
    val scopeSChild = CoroutineScope(SupervisorJob())

    val handler = CoroutineExceptionHandler { context, exception ->
        println("Caught $exception")
    }
    scopeParent.launch(handler) {
        1 / 0
        //child2
    }

    Thread.sleep(2000)


}

Caught java.lang.ArithmeticException: / by zero

例如2:

import kotlinx.coroutines.*

suspend fun main() {
// Scope 控制我的應用中某一層級的協(xié)程
    val scopeParent = CoroutineScope(Job())
    val scopeChild = CoroutineScope(Job())
    val scopeSChild = CoroutineScope(SupervisorJob())

    val handler = CoroutineExceptionHandler { context, exception ->
        println("Caught $exception")
    }
    scopeParent.launch {
        launch(handler) {
            1 / 0
        }
    }

    Thread.sleep(2000)


}

Exception in thread "DefaultDispatcher-worker-2" java.lang.ArithmeticException: / by zero
    at CoroutineKt$main$2$1.invokeSuspend(coroutine.kt:15)
    at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
    at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:56)
    at kotlinx.coroutines.scheduling.CoroutineScheduler.runSafely(CoroutineScheduler.kt:571)
    at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.executeTask(CoroutineScheduler.kt:738)
    at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.runWorker(CoroutineScheduler.kt:678)
    at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.run(CoroutineScheduler.kt:665)

以上協(xié)程的異常為什么沒用被捕捉呢蛇摸?因為我們在協(xié)程的內(nèi)部又啟動了一個子協(xié)程,當子協(xié)程報錯的時候灿巧,直接拋給了父級赶袄,父級不知道handler的存在,所以就沒用捕捉到抠藕。我們可以改成如下:

import kotlinx.coroutines.*

suspend fun main() {
// Scope 控制我的應用中某一層級的協(xié)程
    val scopeParent = CoroutineScope(Job())
    val scopeChild = CoroutineScope(Job())
    val scopeSChild = CoroutineScope(SupervisorJob())

    val handler = CoroutineExceptionHandler { context, exception ->
        println("Caught $exception")
    }
    scopeParent.launch(handler) {
        launch {
            1 / 0
        }
    }

    Thread.sleep(2000)


}
Caught java.lang.ArithmeticException: / by zero

我們讓父級接受handler 就能捕捉到了異常

小結(jié)

  • 協(xié)程內(nèi)部出現(xiàn)異常的時候會直接取消自己和孩子并上報給父親饿肺,最后導致整個作用域取消
  • 讓協(xié)程出現(xiàn)異常不影響其他協(xié)程,我們可以使用SupervisorJob和supervisorScope
  • 處理協(xié)程的異常我們可以使用try catch盾似,當啟動協(xié)程的方法是aysnc的時候敬辣,協(xié)程的異常會在調(diào)用awaite的時候拋出,因此我們的try catch用在調(diào)用awaite
  • 當我們沒有捕捉到異常的時候,這些異掣仍荆可以使用CoroutineExceptionHandler來捕捉村刨。它是上下文的一個可選參數(shù)
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市撰茎,隨后出現(xiàn)的幾起案子嵌牺,更是在濱河造成了極大的恐慌,老刑警劉巖龄糊,帶你破解...
    沈念sama閱讀 207,248評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件逆粹,死亡現(xiàn)場離奇詭異,居然都是意外死亡炫惩,警方通過查閱死者的電腦和手機枯饿,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,681評論 2 381
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來诡必,“玉大人,你說我怎么就攤上這事搔扁“质妫” “怎么了?”我有些...
    開封第一講書人閱讀 153,443評論 0 344
  • 文/不壞的土叔 我叫張陵稿蹲,是天一觀的道長扭勉。 經(jīng)常有香客問我,道長苛聘,這世上最難降的妖魔是什么涂炎? 我笑而不...
    開封第一講書人閱讀 55,475評論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮设哗,結(jié)果婚禮上唱捣,老公的妹妹穿的比我還像新娘。我一直安慰自己网梢,他們只是感情好震缭,可當我...
    茶點故事閱讀 64,458評論 5 374
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著战虏,像睡著了一般拣宰。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上烦感,一...
    開封第一講書人閱讀 49,185評論 1 284
  • 那天巡社,我揣著相機與錄音,去河邊找鬼手趣。 笑死晌该,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播气笙,決...
    沈念sama閱讀 38,451評論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼次企,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了潜圃?” 一聲冷哼從身側(cè)響起缸棵,我...
    開封第一講書人閱讀 37,112評論 0 261
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎谭期,沒想到半個月后堵第,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,609評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡隧出,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,083評論 2 325
  • 正文 我和宋清朗相戀三年踏志,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片胀瞪。...
    茶點故事閱讀 38,163評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡针余,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出凄诞,到底是詐尸還是另有隱情圆雁,我是刑警寧澤,帶...
    沈念sama閱讀 33,803評論 4 323
  • 正文 年R本政府宣布帆谍,位于F島的核電站伪朽,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏汛蝙。R本人自食惡果不足惜烈涮,卻給世界環(huán)境...
    茶點故事閱讀 39,357評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望窖剑。 院中可真熱鬧坚洽,春花似錦、人聲如沸西土。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,357評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽翠储。三九已至绘雁,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間援所,已是汗流浹背庐舟。 一陣腳步聲響...
    開封第一講書人閱讀 31,590評論 1 261
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留住拭,地道東北人挪略。 一個月前我還...
    沈念sama閱讀 45,636評論 2 355
  • 正文 我出身青樓历帚,卻偏偏與公主長得像,于是被迫代替她去往敵國和親杠娱。 傳聞我的和親對象是個殘疾皇子挽牢,可洞房花燭夜當晚...
    茶點故事閱讀 42,925評論 2 344

推薦閱讀更多精彩內(nèi)容