Kotlin协程之异常处理(launch和async的异常处理机制详解)

一 异常传播机制

  1. 先将异常传递给子协程,取消其所有子线程;(取消级联

  2. 而后取消自身;

  3. 再将异常传递给父协程,父协程接收到异常后,会取消其下所有子协程之后取消自身,再向上传递,直到到达最顶层协程。(异常的向上传递

二 异常处理机制

(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:
@Test
    fun coroutineExceptionTest5(): Unit = runBlocking {
        val deferred = GlobalScope.async {
            println("父async协程")
            async {
                delay(200)
                println("子async协程")

            }
            throw Exception("父async协程 异常")

        }

        delay(400)
 
    }

//运行结果
父async协程
情况2:
@Test
    fun 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启动的
 @Test
    fun 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启动的
 @Test
    fun 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进行异常捕获
@Test
    fun 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()调用处进行异常捕获,异常也还是会向上传递
@Test
    fun 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]
在父协程中进行异常处理,捕获未处理的异常,异常不会抛出
 @Test
    fun 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限制异常的向上传递,异常不会传递到父协程抛出
@Test
    fun 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异常捕获
 @Test
    fun 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异常捕获,也不会导致应用崩溃
 @Test
    fun 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协程

若有错误,望请指正!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值