Kotlin协程之异常处理(launch和async的异常处理机制详解)
一 异常传播机制
-
先将异常传递给子协程,取消其所有子线程;(取消级联)
-
而后取消自身;
-
再将异常传递给父协程,父协程接收到异常后,会取消其下所有子协程之后取消自身,再向上传递,直到到达最顶层协程。(异常的向上传递)
二 异常处理机制
(1)try-catch
在kotlin协程中,try-catch的使用是有限制的,不是任何情况都能生效的,例如在launch或async外使用try-catch进行包装,是捕获不到异常的!
一般在协程内部或挂起函数外部(包裹挂起函数)使用
(2)CoroutineExceptionHandler(官方推荐的协程异常处理)
CoroutineExceptionHandler用于捕获未处理的异常,可以在协程作用域创建时或协程启动时作为上下文传入。
三 不同协程构建器启动的协程的默认异常处理
下面讨论的,都是不进行任何异常处理和特殊操作的情况!
(1)launch启动的协程
launch启动的协程遇到异常时,默认情况下会直接抛出。
最终导致协程所在的协程树都收到影响,即同级协程、子协程、和父级协程都收到影响。
(2)async启动的协程
①分为两种情况:异常不一定会抛出,可能被丢弃
-
async作为根协程,即没有父协程:
遇到异常时,依赖用户消费。
-
情况1:用户不调用await(),则异常被丢弃,不会抛出。
-
情况2:用户调用await(),则异常直接在await()处抛出。
-
-
async作为子协程,即有父协程:
遇到异常时,不依赖用户消费。
-
情况3:用户不调用await(),根据异常传播机制,会传递给其子协程后取消自己,再将异常传递给父协程,自下而上由各级父协程决定如何进行异常处理,若父协程是launch启动的且没有进行异常处理,则异常会直接在父协程处抛出;若父协程是async启动的,则返回①继续判断...
-
情况4:用户调用await(),异常会先向上传递,再在await()调用处抛出,故即使在await()调用处进行异常捕获,也无法阻止异常继续向上传递(若想避免这种情况,可以使用SupervisorJob上下文 或 supervisorScope 限制异常的向上传递),即异常还是会传递到父协程,若父协程是launch启动的且没有进行异常处理,则程序会抛出异常;若父协程是async启动的,则返回①继续判断...
-
(3)上述各情况示例代码及运行结果:
情况1:
@Testfun coroutineExceptionTest5(): Unit = runBlocking {val deferred = GlobalScope.async {println("父async协程")async {delay(200)println("子async协程")}throw Exception("父async协程 异常")}delay(400)}//运行结果
父async协程
情况2:
@Testfun coroutineExceptionTest5(): Unit = runBlocking {val deferred = GlobalScope.async {println("父async协程")async {delay(200)println("子async协程")}throw Exception("父async协程 异常")}delay(400)//调用await()方法deferred.await()}//运行结果
父async协程父async协程 异常
java.lang.Exception: 父async协程 异常at com.yl.activitylifecycletest.CoroutineExceptionTest$coroutineExceptionTest5$1$deferred$1.invokeSuspend(CoroutineExceptionTest.kt:122)...
情况3:
父协程是launch启动的
@Testfun coroutineExceptionTest7(): Unit = runBlocking {GlobalScope.launch {println("父launch协程")async {delay(200)println("子async协程")throw Exception("子async协程 异常")}//不调用await()}delay(400)}//运行结果:由于父协程是launch且没有进行异常处理,故直接抛出异常
父launch协程
子async协程
Exception in thread "DefaultDispatcher-worker-1 @coroutine#3" java.lang.Exception: 子async协程 异常at com.yl.activitylifecycletest.CoroutineExceptionTest$coroutineExceptionTest7$1$1$1.invokeSuspend(CoroutineExceptionTest.kt:152)at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:106)at kotlinx.coroutines.scheduling.CoroutineScheduler.runSafely(CoroutineScheduler.kt:570)at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.executeTask(CoroutineScheduler.kt:749)at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.runWorker(CoroutineScheduler.kt:677)at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.run(CoroutineScheduler.kt:664)Suppressed: kotlinx.coroutines.DiagnosticCoroutineContextException: [CoroutineId(2), "coroutine#2":StandaloneCoroutine{Cancelling}@735d482b, Dispatchers.Default]
父协程是async启动的
@Testfun coroutineExceptionTest6(): Unit = runBlocking {GlobalScope.async {println("父async协程")async {delay(200)println("子async协程")throw Exception("子async协程 异常")}//不调用await()}delay(400)}//运行结果:由于父协程是async是根协程,且没有调用await,故异常被丢弃,不抛出
父async协程
子async协程
情况4:
父协程是launch启动的
在await()调用处不进行try-catch进行异常捕获
@Testfun coroutineExceptionTest8(): Unit = runBlocking {GlobalScope.launch {println("父launch协程")val deferred = async {delay(200)println("子async协程")throw Exception("子async协程 异常")}//调用await()deferred.await()}delay(400)}//运行结果:调用await(),异常直接在await()调用处抛出
父launch协程
子async协程
Exception in thread "DefaultDispatcher-worker-1 @coroutine#2" java.lang.Exception: 子async协程 异常at com.yl.activitylifecycletest.CoroutineExceptionTest$coroutineExceptionTest8$1$1$deferred$1.invokeSuspend(CoroutineExceptionTest.kt:166)at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
使用try-catch在await()调用处进行异常捕获,异常也还是会向上传递
@Testfun coroutineExceptionTest8(): Unit = runBlocking {GlobalScope.launch(/*CoroutineExceptionHandler(){_,t->println("CoroutineExceptionHandler 捕获异常 $t")}*/) {println("父launch协程")val deferred = async {delay(200)println("子async协程")throw Exception("子async协程 异常")}//调用await(),并使用try-catch进行异常捕获try {deferred.await()}catch (e:Exception){println("try catch 捕获异常 $e")}}delay(400)}//运行结果:由于异常是先向上传递再在await调用处抛出的,
//故即使try-catch捕获到了异常,但是异常还是向上传递到父协程,
//又因为父协程是launch启动的,且没有进行异常处理,故而异常还是抛出父launch协程
子async协程
try catch 捕获异常 java.lang.Exception: 子async协程 异常
Exception in thread "DefaultDispatcher-worker-1 @coroutine#2" java.lang.Exception: 子async协程 异常at com.yl.activitylifecycletest.CoroutineExceptionTest$coroutineExceptionTest8$1$1$deferred$1.invokeSuspend(CoroutineExceptionTest.kt:168)at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:106)at kotlinx.coroutines.scheduling.CoroutineScheduler.runSafely(CoroutineScheduler.kt:570)at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.executeTask(CoroutineScheduler.kt:749)at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.runWorker(CoroutineScheduler.kt:677)at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.run(CoroutineScheduler.kt:664)Suppressed: kotlinx.coroutines.DiagnosticCoroutineContextException: [CoroutineId(2), "coroutine#2":StandaloneCoroutine{Cancelling}@6e8881d6, Dispatchers.Default]
在父协程中进行异常处理,捕获未处理的异常,异常不会抛出
@Testfun coroutineExceptionTest8(): Unit = runBlocking {GlobalScope.launch(CoroutineExceptionHandler() { _, t ->println("CoroutineExceptionHandler 捕获异常 $t")}) {println("父launch协程")val deferred = async {delay(200)println("子async协程")throw Exception("子async协程 异常")}//调用await(),并使用try-catch进行异常捕获try {deferred.await()} catch (e: Exception) {println("try catch 捕获异常 $e")}}delay(400)}//运行结果:在父协程进行了异常处理,异常被父协程成功捕获拦截,不会抛出
父launch协程
子async协程
try catch 捕获异常 java.lang.Exception: 子async协程 异常
CoroutineExceptionHandler 捕获异常 java.lang.Exception: 子async协程 异常
使用supervisorScope限制异常的向上传递,异常不会传递到父协程抛出
@Testfun coroutineExceptionTest8(): Unit = runBlocking {GlobalScope.launch(/*CoroutineExceptionHandler() { _, t ->println("CoroutineExceptionHandler 捕获异常 $t")}*/) {println("父launch协程")//使用supervisorScope 限制异常向上传递supervisorScope {val deferred = async {delay(200)println("子async协程")throw Exception("子async协程 异常")}//调用await(),并使用try-catch进行异常捕获try {deferred.await()} catch (e: Exception) {println("try catch 捕获异常 $e")}}}delay(400)}//运行结果:异常没有向上传递到父launch协程抛出父launch协程
子async协程
try catch 捕获异常 java.lang.Exception: 子async协程 异常
父协程是async启动的
调用子async协程的await(),并对其进行try-catch异常捕获
@Testfun coroutineExceptionTest9(): Unit = runBlocking {GlobalScope.async() {println("父async协程")val deferred = async {delay(200)println("子async协程")throw Exception("子async协程 异常")}//调用await()try {deferred.await()}catch (e:Exception){println("try-catch 捕获异常 $e")}}delay(2000)}//运行结果:此处我们使用try-catch对await()进行包装,捕获到了其抛出的异常,
//由于父协程并没有调用await(),故异常不会抛出导致应用崩溃。
父async协程
子async协程
try-catch 捕获异常 java.lang.Exception: 子async协程 异常
调用子async协程的await(),但不对其进行try-catch异常捕获,也不会导致应用崩溃
@Testfun coroutineExceptionTest10(): Unit = runBlocking {GlobalScope.async() {println("父async协程")val deferred = async {delay(200)println("子async协程")throw Exception("子async协程 异常")}//调用await()deferred.await()}delay(2000)}//运行结果:是不是很出乎意料~居然没有抛出异常导致应用崩溃!
//但是我们刚刚进行异常捕获的时候,明明在deferred.await()处是可以捕获到异常的
//猜测原因:
//deferred.await()处位于父协程内部,故而抛出异常的位置是在于父协程内部的,
//而GlobalScope有默认的全局异常处理器CoroutineExceptionHandler,进行异常处理了,
//故而没有抛异常!父async协程
子async协程
若有错误,望请指正!