kotlin 協(xié)程上下文異常處理詳解
引言
從前面我們可以大致了解了協(xié)程的玩法,如果一個(gè)協(xié)程中使用子協(xié)程,那么該協(xié)程會(huì)等待子協(xié)程執(zhí)行結(jié)束后才真正退出,而達(dá)到這種效果的原因就是協(xié)程上下文,上下文貫穿了協(xié)程的生命周期,這套思想和我們app的上下文很像
在開始真正了解協(xié)程上下文之前,我們先來看看下面的例子
下面的圖代表了一個(gè)協(xié)程a的生命,就像一條從上至下的直線,它的生命只有100ms
當(dāng)我們在a協(xié)程延遲函數(shù)100ms之前開啟一個(gè)子協(xié)程b,b做了200ms的事情,如果不考慮調(diào)度消耗的時(shí)間,那么a協(xié)程的生命也會(huì)延長成200ms
代碼驗(yàn)證下:
fun `test context life`() = runBlocking { //定義一個(gè)作用域 val a = CoroutineScope(Dispatchers.Default) val startTime = System.currentTimeMillis() //協(xié)程a開啟 val jobA = a.launch { //子協(xié)程b開啟 val jobB = launch { delay(200) } delay(100) } //等待協(xié)程a結(jié)束 jobA.join() val endTime = System.currentTimeMillis() println(endTime - startTime) } fun main() { `test context life`() }
結(jié)果:237
如果我們把子協(xié)程b增加到delay 300ms,那么結(jié)果也會(huì)相應(yīng)的變?yōu)椋?/p>
323
通過上面的列子,來對協(xié)程上下文的有一個(gè)初步概念:可以說協(xié)程的生命周期,就是上下文的生命周期
協(xié)程擁有很多新的概念,很多人一開始接觸就能難理解(包括我自己),這些概念都是在上下文的基礎(chǔ)上引申而來的,所以我一再強(qiáng)調(diào)它的重要性,協(xié)程的上下文必須理解透,才能玩好協(xié)程,接下來我們來真正了解協(xié)程上下文
一、協(xié)程上下文
1.CoroutineContext
協(xié)程上下文有以下幾項(xiàng)構(gòu)成,它們都是實(shí)現(xiàn)了CoroutineContext.Element接口,有些是實(shí)現(xiàn)了AbstractCoroutineContextElement接口,而AbstractCoroutineContextElement繼承CoroutineContext.Element接口
1.Job:控制協(xié)程的生命周期,也是我們能拿到操作協(xié)程任務(wù)的唯一對象
2.CoroutineDispatcher:就是之前介紹的調(diào)度器
3.CoroutineName:協(xié)程的名字,一般輸出日志用的
4.CoroutineExceptionHandler:處理未捕獲的異常
協(xié)程上下文實(shí)現(xiàn)了運(yùn)算符重載,我們可以用+號來組合一個(gè)CoroutineContext的元素
2.CorountineScope
一般情況下,協(xié)程體內(nèi)所有的子協(xié)程,都繼承至根協(xié)程,協(xié)程的繼承的關(guān)系不是我們所了解的類的繼承關(guān)系,而是父協(xié)程和子協(xié)程的生命周期關(guān)系,還記得我們上面舉得例子么,除非在協(xié)程體內(nèi)自己手動(dòng)創(chuàng)建協(xié)程作用域,即:創(chuàng)建一個(gè)全新的協(xié)程上下文,我們之前已經(jīng)介紹過了:
CorountineScope:創(chuàng)建協(xié)程作用域,新起線程,觀察源碼,內(nèi)部實(shí)際實(shí)例化的是ContextScope,ContextScope被internal修飾,內(nèi)部使用,我們實(shí)例化不了
其他的實(shí)際上都是繼承父協(xié)程上下文,或者內(nèi)部實(shí)例化了ContextScope:
1.runBlocking:將主線程轉(zhuǎn)變?yōu)閰f(xié)程,會(huì)阻塞主線程,實(shí)際上用的是一個(gè)EmptyCoroutineContext作為上下文,它是一個(gè)主線程的協(xié)程上下文,靜態(tài)的全局變量,我們其實(shí)就可以理解成是主線程
2.GlobalScope:也是用的EmptyCoroutineContext
3.MainScope:使用ContextScope構(gòu)造了新的上下文
4.coroutineScope:繼承的父協(xié)程上下文,不能算是全新的協(xié)程
等等
3.子協(xié)程繼承父協(xié)程
子協(xié)程繼承父協(xié)程時(shí),除了Job會(huì)自動(dòng)創(chuàng)建新的實(shí)例外,其他3項(xiàng)的不手動(dòng)指定的話,都會(huì)自動(dòng)繼承父協(xié)程的,Job對應(yīng)的是協(xié)程任務(wù),每次新的任務(wù)肯定都是新的Job對象
有了這些概念后,接下來通過代碼,再熟悉鞏固下
例子1:
fun `test context life1`() = runBlocking { //定義一個(gè)作用域 val a = CoroutineScope(Dispatchers.Default) //協(xié)程a開啟 val jobA = a.launch { delay(100) println("jobA finished") } println("main finished") }
結(jié)果:
main finished
由于a是一個(gè)根協(xié)程,全新的上下文,runBlocking 是主線程的協(xié)程上下文,所以當(dāng)a開啟任務(wù)時(shí),不會(huì)阻塞主線程,當(dāng)我們的進(jìn)程都跑完了,jobA finished肯定不會(huì)打印了
例子2:
fun `test context life2`() = runBlocking { //定義一個(gè)作用域 val a = CoroutineScope(Dispatchers.Default) //協(xié)程a開啟 val jobA = a.launch { delay(100) println("jobA finished") } jobA.join() println("main finished") }
結(jié)果:
jobA finished
main finished
我們在主協(xié)程(主線程的協(xié)程)中,手動(dòng)調(diào)用jobA的join方法,那么主線程就會(huì)阻塞,直到j(luò)obA執(zhí)行完畢。這個(gè)和我們的多線程操作是一樣的,主線程等待A線程執(zhí)行完后再往后執(zhí)行
例子3:
fun `test context life3`() = runBlocking { launch { delay(100) println("jobA finished") } println("main finished") }
結(jié)果:
main finished
jobA finished
這回我們沒有構(gòu)建新的協(xié)程作用域,而是在根協(xié)程中直接使用子協(xié)程的方式,當(dāng)然了,協(xié)程的上下文繼承關(guān)系,使得我們的主協(xié)程等待子協(xié)程執(zhí)行完畢后才結(jié)束生命
例子4:
fun `test context life4`() = runBlocking { launch(Dispatchers.IO + CoroutineName("jobA")) { delay(100) println("${coroutineContext[CoroutineName]} finished") } println("main finished") }
結(jié)果:
main finished
CoroutineName(jobA) finished
即使我們指定了子協(xié)程的調(diào)度器和協(xié)程名,也不會(huì)影響協(xié)程上下文繼承關(guān)系,主協(xié)程還是會(huì)等待子協(xié)程執(zhí)行完畢后才結(jié)束生命
如果你已經(jīng)完全理解了,那么就可以知道以上例子使用async啟動(dòng)也是一樣的效果
二、協(xié)程的異常傳遞
1.協(xié)程的異常傳播
協(xié)程的異常傳播也是遵循了協(xié)程上下文的機(jī)制,除了取消異常(CancellationException)外,當(dāng)一個(gè)協(xié)程有了異常,如果沒有主動(dòng)捕獲異常,那么異常會(huì)向上傳播,直到根協(xié)程,子協(xié)程的異常都會(huì)導(dǎo)致根協(xié)程退出,自然其他子協(xié)程也會(huì)退出
例子1:
fun `test coroutineScope exception1`() = runBlocking { val job1 = launch { delay(2000) println("job finished") } val job2 = launch { delay(1000) println("job2 finished") throw IllegalArgumentException() } delay(3000) println("finished") }
結(jié)果:
job2 finished
Exception in thread "main" java.lang.IllegalArgumentException
at com.aruba.mykotlinapplication.coroutine.ExceptionTestKt$test coroutineScope exception1$1$job2$1.invokeSuspend(exceptionTest.kt:46)
at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
at kotlinx.coroutines.ResumeModeKt.resumeMode(ResumeMode.kt:67)
at kotlinx.coroutines.DispatchedKt.resume(Dispatched.kt:309)
at kotlinx.coroutines.DispatchedKt.dispatch(Dispatched.kt:298)
at kotlinx.coroutines.CancellableContinuationImpl.dispatchResume(CancellableContinuationImpl.kt:250)
at kotlinx.coroutines.CancellableContinuationImpl.resumeImpl(CancellableContinuationImpl.kt:260)
at kotlinx.coroutines.CancellableContinuationImpl.resumeUndispatched(CancellableContinuationImpl.kt:332)
at kotlinx.coroutines.EventLoopImplBase$DelayedResumeTask.run(EventLoop.kt:298)
at kotlinx.coroutines.EventLoopImplBase.processNextEvent(EventLoop.kt:116)
at kotlinx.coroutines.BlockingCoroutine.joinBlocking(Builders.kt:80)
at kotlinx.coroutines.BuildersKt__BuildersKt.runBlocking(Builders.kt:54)
at kotlinx.coroutines.BuildersKt.runBlocking(Unknown Source)
at kotlinx.coroutines.BuildersKt__BuildersKt.runBlocking$default(Builders.kt:36)
at kotlinx.coroutines.BuildersKt.runBlocking$default(Unknown Source)
at com.aruba.mykotlinapplication.coroutine.ExceptionTestKt.test coroutineScope exception1(exceptionTest.kt:37)
at com.aruba.mykotlinapplication.coroutine.ExceptionTestKt.main(exceptionTest.kt:54)
at com.aruba.mykotlinapplication.coroutine.ExceptionTestKt.main(exceptionTest.kt)Process finished with exit code 1
job2 1000ms后就發(fā)生了異常,導(dǎo)致job1和父協(xié)程都直接退出
2.不同上下文(沒有繼承關(guān)系)之間協(xié)程異常會(huì)怎么樣?
例子1:
fun `test coroutineScope exception2`() = runBlocking { val job1 = launch { delay(2000) println("job finished") } val job2 = CoroutineScope(Dispatchers.IO).launch{ delay(1000) println("job2 finished") throw IllegalArgumentException() println("new CoroutineScope finished") } delay(3000) println("finished") }
結(jié)果:
job2 finished
Exception in thread "DefaultDispatcher-worker-2" java.lang.IllegalArgumentException
at com.aruba.mykotlinapplication.coroutine.ExceptionTestKt$test coroutineScope exception1$1$job2$1.invokeSuspend(exceptionTest.kt:46)
at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
at kotlinx.coroutines.DispatchedTask.run(Dispatched.kt:238)
at kotlinx.coroutines.scheduling.CoroutineScheduler.runSafely(CoroutineScheduler.kt:594)
at kotlinx.coroutines.scheduling.CoroutineScheduler.access$runSafely(CoroutineScheduler.kt:60)
at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.run(CoroutineScheduler.kt:742)
job finished
finishedProcess finished with exit code 0
可以看出不同根協(xié)程的協(xié)程之間,異常并不會(huì)自動(dòng)傳遞,我們的主線程上下文協(xié)程正常執(zhí)行
再看例子2:
fun `test coroutineScope exception3`() = runBlocking { val job1 = launch { delay(2000) println("job finished") } val job2 = CoroutineScope(Dispatchers.IO).async{ delay(1000) println("job2 finished") throw IllegalArgumentException() println("new CoroutineScope finished") } delay(3000) println("finished") }
結(jié)果:
job2 finished
job finished
finished
和例子1的唯一區(qū)別是,使用了全新上下文的協(xié)程使用了async啟動(dòng),哈哈,這就奇怪了,為什么會(huì)這樣?
3.向用戶暴露異常
還記得async啟動(dòng)的協(xié)程返回的是一個(gè)Deferred么,它可以使用await函數(shù),來獲取協(xié)程運(yùn)行結(jié)果。那么試想一下,如果我就是想要一個(gè)協(xié)程執(zhí)行完返回一個(gè)異常呢?
所以async中的異常會(huì)作為返回值,返回給調(diào)用await函數(shù)
fun `test coroutineScope exception4`() = runBlocking { val job1 = launch { delay(2000) println("job finished") } val job2 = CoroutineScope(Dispatchers.IO).async{ delay(1000) println("job2 finished") throw IllegalArgumentException() println("new CoroutineScope finished") } job2.await() delay(3000) println("finished") }
結(jié)果:
job2 finished
Exception in thread "main" java.lang.IllegalArgumentException
at com.aruba.mykotlinapplication.coroutine.ExceptionTestKt$test coroutineScope exception4$1$job2$1.invokeSuspend(exceptionTest.kt:96)
at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
at kotlinx.coroutines.DispatchedTask.run(Dispatched.kt:238)
at kotlinx.coroutines.scheduling.CoroutineScheduler.runSafely(CoroutineScheduler.kt:594)
at kotlinx.coroutines.scheduling.CoroutineScheduler.access$runSafely(CoroutineScheduler.kt:60)
at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.run(CoroutineScheduler.kt:742)Process finished with exit code 1
await的時(shí)候出現(xiàn)異常了,當(dāng)然會(huì)導(dǎo)致協(xié)程退出,我們可以在await的時(shí)候捕獲下這個(gè)異常,就不會(huì)影響主線程上下文的協(xié)程運(yùn)行了
fun `test coroutineScope exception4`() = runBlocking { val job1 = launch { delay(2000) println("job finished") } val job2 = CoroutineScope(Dispatchers.IO).async { delay(1000) println("job2 finished") throw IllegalArgumentException() println("new CoroutineScope finished") } try { job2.await() } catch (e: Exception) { e.printStackTrace() } delay(3000) println("finished") }
結(jié)果:
job2 finished
java.lang.IllegalArgumentException
at com.aruba.mykotlinapplication.coroutine.ExceptionTestKt$test coroutineScope exception4$1$job2$1.invokeSuspend(exceptionTest.kt:96)
at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
at kotlinx.coroutines.DispatchedTask.run(Dispatched.kt:238)
at kotlinx.coroutines.scheduling.CoroutineScheduler.runSafely(CoroutineScheduler.kt:594)
at kotlinx.coroutines.scheduling.CoroutineScheduler.access$runSafely(CoroutineScheduler.kt:60)
at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.run(CoroutineScheduler.kt:742)
job finished
finishedProcess finished with exit code 0
值得注意的是,同一繼承關(guān)系下的協(xié)程使用await并無法捕獲異常,還是會(huì)遵循第一條,導(dǎo)致整個(gè)協(xié)程生命周期結(jié)束
fun `test coroutineScope exception5`() = runBlocking { val job2 = CoroutineScope(Dispatchers.IO).launch { val job1 = launch { delay(2000) println("job finished") } val job3 = async { delay(1000) println("job3 finished") throw IllegalArgumentException() } try { job3.await() } catch (e: Exception) { e.printStackTrace() } delay(2000) println("job2 finished") } job2.join() println("finished") }
結(jié)果:
job3 finished
java.lang.IllegalArgumentException
at com.aruba.mykotlinapplication.coroutine.ExceptionTestKt$test coroutineScope exception5$1$job2$1$job3$1.invokeSuspend(exceptionTest.kt:119)
at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
at kotlinx.coroutines.DispatchedTask.run(Dispatched.kt:238)
at kotlinx.coroutines.scheduling.CoroutineScheduler.runSafely(CoroutineScheduler.kt:594)
at kotlinx.coroutines.scheduling.CoroutineScheduler.access$runSafely(CoroutineScheduler.kt:60)
at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.run(CoroutineScheduler.kt:742)
Exception in thread "DefaultDispatcher-worker-1" java.lang.IllegalArgumentException
at com.aruba.mykotlinapplication.coroutine.ExceptionTestKt$test coroutineScope exception5$1$job2$1$job3$1.invokeSuspend(exceptionTest.kt:119)
at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
at kotlinx.coroutines.DispatchedTask.run(Dispatched.kt:238)
at kotlinx.coroutines.scheduling.CoroutineScheduler.runSafely(CoroutineScheduler.kt:594)
at kotlinx.coroutines.scheduling.CoroutineScheduler.access$runSafely(CoroutineScheduler.kt:60)
at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.run(CoroutineScheduler.kt:742)
finishedProcess finished with exit code 0
可以發(fā)現(xiàn)job3.await()的try catch并沒有生效,所以向用戶暴露異常只適用于不同上下文(沒有繼承關(guān)系)的協(xié)程
三、協(xié)程的異常處理
使用SupervisorJob
如果想要一個(gè)協(xié)程出現(xiàn)異常后,不影響其繼承關(guān)系中的其他協(xié)程,可以使用SupervisorJob
fun `test SupervisorJob exception`() = runBlocking { val job1 = launch { delay(2000) println("job finished") } val job2 = async(SupervisorJob()) { delay(1000) println("job2 finished") throw IllegalArgumentException() } delay(3000) println("finished") }
結(jié)果:
job2 finished
job finished
finished
可以看到,job2的異常并沒有影響其他繼承關(guān)系的協(xié)程的執(zhí)行
SupervisorScope,這個(gè)我們前面已經(jīng)用過了,就不重復(fù)介紹了
異常捕獲器CoroutineExceptionHandler
協(xié)程上下文的4項(xiàng)之一,可以用CrashHandler理解,不過它并不能阻止協(xié)程的退出,只能夠獲取異常的信息
它使用有兩個(gè)條件:
1.異常是自動(dòng)拋出異常(launch)
2.實(shí)例化CoroutineScope的時(shí)候指定異常捕獲器 或者 在一個(gè)根協(xié)程中
例子1:
fun `test SupervisorHandler exception1`() = runBlocking { val handler = CoroutineExceptionHandler { _, throwable -> println("caught: $throwable") } val scope = CoroutineScope(handler) val job1 = scope.launch { val job2 = launch { delay(1000) println("job2 finished") throw IllegalArgumentException() } delay(2000) println("job finished") } delay(4000) println("finished") }
結(jié)果:
job2 finished
caught: java.lang.IllegalArgumentException
finished
job2拋出了異常,被捕獲到了,但是scope的其他協(xié)程隨之生命周期也都結(jié)束了
例子2:
fun `test SupervisorHandler exception2`() = runBlocking { val handler = CoroutineExceptionHandler { _, throwable -> println("caught: $throwable") } val scope = CoroutineScope(Dispatchers.Default) val job1 = scope.launch(handler) { val job2 = launch { delay(1000) println("job2 finished") throw IllegalArgumentException() } delay(2000) println("job finished") } delay(4000) println("finished") }
結(jié)果:
job2 finished
caught: java.lang.IllegalArgumentException
finished
和例子1相同,因?yàn)槲覀僪andler指定在了根協(xié)程
例子3:
fun `test SupervisorHandler exception3`() = runBlocking { val handler = CoroutineExceptionHandler { _, throwable -> println("caught: $throwable") } val scope = CoroutineScope(Dispatchers.Default) val job1 = scope.launch { val job2 = launch(handler) { delay(1000) println("job2 finished") throw IllegalArgumentException() } delay(2000) println("job finished") } delay(4000) println("finished") }
結(jié)果:
job2 finished
Exception in thread "DefaultDispatcher-worker-4" java.lang.IllegalArgumentException
at com.aruba.mykotlinapplication.coroutine.ExceptionTestKt$test SupervisorHandler exception$1$job1$1$job2$1.invokeSuspend(exceptionTest.kt:161)
at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
at kotlinx.coroutines.DispatchedTask.run(Dispatched.kt:238)
at kotlinx.coroutines.scheduling.CoroutineScheduler.runSafely(CoroutineScheduler.kt:594)
at kotlinx.coroutines.scheduling.CoroutineScheduler.access$runSafely(CoroutineScheduler.kt:60)
at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.run(CoroutineScheduler.kt:742)
finishedProcess finished with exit code 0
handler不是在根協(xié)程中,不能捕獲
如果一個(gè)子協(xié)程會(huì)拋出異常,那么對它進(jìn)行等待時(shí)(join或await),包裹一層try catch 會(huì)出現(xiàn)意料之外的事
例子4:
fun `test SupervisorHandler exception4`() = runBlocking { val handler = CoroutineExceptionHandler { _, throwable -> println("caught: $throwable") } val scope = CoroutineScope(Dispatchers.Default) val job1 = scope.launch(handler) { val job2 = launch { delay(1000) println("job2 finished") throw IllegalArgumentException() } try { job2.join() }catch (e:Exception){ } // val job3 = scope.launch { // println("job3 finished") // } println("job delay") delay(2000) for(i in 0..10){ println(i) } println("job finished") } delay(4000) println("finished") }
結(jié)果:
job2 finished
job delay
caught: java.lang.IllegalArgumentException
finished
如果把scope根協(xié)程中的delay函數(shù)注釋掉,會(huì)怎么樣呢?
fun `test SupervisorHandler exception4`() = runBlocking { val handler = CoroutineExceptionHandler { _, throwable -> println("caught: $throwable") } val scope = CoroutineScope(Dispatchers.Default) val job1 = scope.launch(handler) { val job2 = launch { delay(1000) println("job2 finished") throw IllegalArgumentException() } try { job2.join() }catch (e:Exception){ } // val job3 = scope.launch { // println("job3 finished") // } println("job delay") // delay(2000) for(i in 0..10){ println(i) } println("job finished") } delay(4000) println("finished") }
結(jié)果:
job2 finished
job delay
0
1
2
3
4
5
6
7
8
9
10
job finished
caught: java.lang.IllegalArgumentException
如果不包裹try catch 那么println("job delay")都不會(huì)執(zhí)行
由例子4和例子5,我們可以推斷,如果子協(xié)程有異常發(fā)生了,我們在等待時(shí)捕獲異常后,根協(xié)程執(zhí)行了掛起函數(shù),那么它會(huì)直接中斷,不執(zhí)行掛起函數(shù)以下的代碼,如果沒有掛起函數(shù),那么后面的代碼還是會(huì)執(zhí)行
為了加強(qiáng)驗(yàn)證這點(diǎn),我們使用Thread.sleep(2000)替換delay函數(shù)測試下:
fun `test SupervisorHandler exception4`() = runBlocking { val handler = CoroutineExceptionHandler { _, throwable -> println("caught: $throwable") } val scope = CoroutineScope(Dispatchers.Default) val job1 = scope.launch(handler) { val job2 = launch { delay(1000) println("job2 finished") throw IllegalArgumentException() } try { job2.join() }catch (e:Exception){ } // val job3 = scope.launch { // println("job3 finished") // } println("job delay") // delay(2000) Thread.sleep(2000) for(i in 0..10){ println(i) } println("job finished") } delay(4000) println("finished") }
結(jié)果還是和例子5一樣:
job2 finished
job delay
0
1
2
3
4
5
6
7
8
9
10
job finished
caught: java.lang.IllegalArgumentException
finished
Process finished with exit code 0
其實(shí)出現(xiàn)這個(gè)情況,和我們之前取消協(xié)程是一樣的,出現(xiàn)異常后會(huì)開始取消協(xié)程,但是CPU密集型的代碼還會(huì)執(zhí)行,但是遇到掛起函數(shù)就會(huì)拋一個(gè)CancellationException,導(dǎo)致協(xié)程結(jié)束運(yùn)行,如果我們在掛起函數(shù)加上try catch打印,那么我們就可以看到CancellationException了
例子6,把job3的注釋放開:
fun `test SupervisorHandler exception4`() = runBlocking { val handler = CoroutineExceptionHandler { _, throwable -> println("caught: $throwable") } val scope = CoroutineScope(Dispatchers.Default) val job1 = scope.launch(handler) { val job2 = launch { delay(1000) println("job2 finished") throw IllegalArgumentException() } try { job2.join() }catch (e:Exception){ } val job3 = scope.launch { println("job3 finished") } println("job delay") delay(2000) // Thread.sleep(2000) for(i in 0..10){ println(i) } println("job finished") } delay(4000) println("finished") }
結(jié)果:
job2 finished
job delay
caught: java.lang.IllegalArgumentException
Exception in thread "DefaultDispatcher-worker-1" java.lang.IllegalArgumentException
at com.aruba.mykotlinapplication.coroutine.ExceptionTestKt$test SupervisorHandler exception4$1$job1$1$job2$1.invokeSuspend(exceptionTest.kt:227)
at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
at kotlinx.coroutines.DispatchedTask.run(Dispatched.kt:238)
at kotlinx.coroutines.scheduling.CoroutineScheduler.runSafely(CoroutineScheduler.kt:594)
at kotlinx.coroutines.scheduling.CoroutineScheduler.access$runSafely(CoroutineScheduler.kt:60)
at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.run(CoroutineScheduler.kt:742)
finishedProcess finished with exit code 0
顯然有異常沒有被捕獲,很明顯這個(gè)異常是調(diào)用job3時(shí)輸出的,由此又可以推斷出,如果在等待任務(wù)結(jié)束時(shí),任務(wù)出現(xiàn)異常并且手動(dòng)捕獲異常后,再啟動(dòng)子協(xié)程時(shí),也會(huì)拋出異常,并且不可捕獲
注意:新版本kotlin已修復(fù)這個(gè)bug,不會(huì)拋出異常了
Android中全局異常的處理
最后,感謝動(dòng)腦學(xué)院Jason老師出的kotlin協(xié)程教程,得到了很多理解和啟發(fā)
以上就是kotlin 協(xié)程上下文異常處理詳解的詳細(xì)內(nèi)容,更多關(guān)于kotlin 協(xié)程上下文異常處理的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Android SeekBar 自定義thumb旋轉(zhuǎn)動(dòng)畫效果
某些音樂播放或者視頻播放的界面上,資源還在加載時(shí),進(jìn)度條的原點(diǎn)(thumb)會(huì)顯示一個(gè)轉(zhuǎn)圈的效果。這篇文章主要介紹了Android SeekBar 自定義thumb thumb旋轉(zhuǎn)動(dòng)畫效果,需要的朋友可以參考下2021-11-11Android中Listview點(diǎn)擊item不變顏色及設(shè)置listselector 無效的解決方案
這篇文章主要介紹了Android中Listview點(diǎn)擊item不變顏色及設(shè)置listselector 無效的原因及解決方案,非常不錯(cuò),具有參考借鑒價(jià)值,需要的朋友可以參考下2016-09-09搭建簡易藍(lán)牙定位系統(tǒng)的實(shí)現(xiàn)方法
下面小編就為大家?guī)硪黄罱ê喴姿{(lán)牙定位系統(tǒng)的實(shí)現(xiàn)方法。小編覺得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2017-03-03Android開發(fā)自學(xué)筆記(一):Hello,world!
這篇文章主要介紹了Android開發(fā)自學(xué)筆記(一):Hello,world!本文講解了創(chuàng)建HelloWorld工程、編寫代碼、啟動(dòng)模擬器等步驟,需要的朋友可以參考下2015-04-04Android編程實(shí)現(xiàn)扭曲圖像的繪制功能示例
這篇文章主要介紹了Android編程實(shí)現(xiàn)扭曲圖像的繪制功能,結(jié)合實(shí)例形式較為詳細(xì)的分析了Android圖形扭曲的具體操作步驟與相關(guān)實(shí)現(xiàn)技巧,需要的朋友可以參考下2017-09-09Android?Studio?2022.1.1創(chuàng)建項(xiàng)目的Gradle配置問題
這篇文章主要介紹了Android?Studio?2022.1.1創(chuàng)建項(xiàng)目的Gradle配置問題,本文給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2023-04-04在RecyclerView中實(shí)現(xiàn)button的跳轉(zhuǎn)功能
本次實(shí)驗(yàn)就是在RecyclerView中添加一個(gè)button控件并實(shí)現(xiàn)監(jiān)聽,使鼠標(biāo)點(diǎn)擊時(shí)可以跳轉(zhuǎn)到另外一個(gè)設(shè)計(jì)好的界面,對RecyclerView實(shí)現(xiàn)button跳轉(zhuǎn)功能感興趣的朋友一起看看吧2021-10-10Android ViewFlipper翻轉(zhuǎn)視圖使用詳解
這篇文章主要為大家詳細(xì)介紹了Android ViewFlipper翻轉(zhuǎn)視圖的使用方法,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-05-05