Kotlin 协程 - 协程异常处理器 CoroutineExceptionHandler

一、异常的传播

  • Job的取消和异常传播是双向的(结构化并发),如果异常在局部没有捕获处理而被协程抛出,该协程会先cancel所有子协程再cancl自己,如果这个异常是 CancellationException 类型便终止向上传播,如果使用了 SupervisorScope() 或 supervisorJob 不管什么类型的异常都终止向上传播,否则会一直传递到根协程导致整个结构中的协程都会被cancel。
  • CancellationException类型的异常由抛出的协程处理,SupervisorJob类型时由该层的下级子协程处理(抛异常的协程或者父协程,但是都是SupervisorJob层的子协程),其他情况由根协程处理。

二、打断传播

当不希望异常向上传播或影响兄弟协程时使用(向下传播依然存在)。

2.1 supervisorJob

内部发生异常不cancel父协程及兄弟协程,也不会受他们影响,但内部的子协程发生异常会相互影响。异常不处理会导致程序崩溃。

Kotlin 复制代码
//避免在只有一个子协程的时候传参使用(孙协程仍会相互取消,作用相当于普通job)
launch(SupervisorJob()) {    //唯一子协程
    launch {
        println("子协程1")
        throw Exception()    //会相互取消
    }   
    launch {
        println("子协程2")    //不会打印
    }
}
//正确写法
val job = SupervisorJob()
launch(job) {
    println("子协程1")
    throw Exception()    //不会相互取消
}
launch(job) {
    println("子协程2")    //会打印
}

2.2 SupervisorScope()

Kotlin 复制代码
supervisorScope{
    launch{
        println("子协程1")
        throw Exception()
    }
    launch{
        println("子协程2")    //会打印
    }
}

三、CancellationException

如果异常是 CancellationException 及其子类,将不会向上传递,只取消当前协程及其子类。

Kotlin 复制代码
//分别开启1和2两个协程,抛出异常会结束1和它的子协程3,但不会影响2
object MyException : CancellationException()
suspend fun main(): Unit = coroutineScope {
    launch {    //1
        launch {    //3
            delay(2000)
            println("1")    //不会打印
        }
        throw MyException
    }
    launch {    //2
        delay(2000)
        println("2")    //会打印
    }
}

四、异常捕获

|--------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| launch | 代码块中抛出的异常直接抛出。 |
| async | 代码块中抛出的异常通过最终消费即调用await()才抛出,子协程中的异常不受await()影响,未捕获会向上传递给根协程处理。所以对每个 await() 单独捕获是避免崩溃影响其它并发任务,再捕获全部async是避免子协程异常向上传递导致程序崩溃(也可以在外面套一层会抛异常不向上传递的coroutineScope()再捕获),或者使用CoroutineExceptionHandler。 |

4.1 try-catch

  • 捕获协程构建器无效,要捕获协程代码块中 throw 的异常:如果协程代码块中 throw 的异常没有被捕获处理,就会在 BaseContinuationImpl.resumeWith() 中被捕获封装成 Result 对象传递,最终传递给异常处理器,不会再次throw,也就没有异常可捕获了,也就是构建器不抛异常。

  • 能捕获到挂起函数中子线程的异常:try捕获子线程是无效的,只能捕获当前线程的堆栈信息。在协程中能捕获到开启了子线程的挂起函数中的异常,是因为挂起函数底层代码通过 reusmeWithExceptoon() 携带异常从子线程恢复到当前线程抛出,不然直接 throw 是捕获不到的还会导致永远挂起。

4.2 CoroutineExceptionHandler

  • 异常会一直向上传播到根协程,根协程如果不作响应(SupervisorScope()或 supervisorJob),则直接子协程会在上下文中寻找 CoroutineExceptionHandler 处理,否则走 UncaughtExceptionHandler,所以其它子协程中的 CoroutineExceptionHandler 不会起作用。
  • 使用条件:要么存在于协程作用域的上下文中、要么存在于根协程的直接子协程中。
  • 只能处理当前作用域中的异常,例如代码块中调用了其它作用域对象开启的协程,发生的异常需要在该作用域对象的上下文中配置CoroutineExceptionHandler处理。
Kotlin 复制代码
//不会阻止异常传递,但可以定义在发生异常时的处理行为(默认情况它会打印异常堆栈)
fun main(): Unit = runBlocking {
    val parentExceptionHandler = CoroutineExceptionHandler { _, throwable -> println("调用根协程异常处理器:${throwable.message}") }
    val childExceptionHandler = CoroutineExceptionHandler { _, throwable -> println("调用子协程异常处理器:${throwable.message}") }
    CoroutineScope().launch(parentExceptionHandler) {
        launch(childExceptionHandler) {
            throw Exception("子协程使用的是Job")    //打印:调用根协程异常处理器
        }
    }
    CoroutineScope().launch(parentExceptionHandler) {
        launch(SupervisorJob() + childExceptionHandler) {
            throw Exception("子协程使用的是SupervisorJob")    //打印:调用子协程异常处理器
        }
    }
    delay(1000)
}
相关推荐
小金子同志4 小时前
发现 Kotlin MultiPlatform 的一点小变化
kotlin
androidwork6 小时前
嵌套滚动交互处理总结
android·java·kotlin
橙子199110168 小时前
Kotlin 中的 Object
android·开发语言·kotlin
岸芷漫步1 天前
Kotlin中协程的关键函数分析
kotlin
纳于大麓1 天前
Kotlin基础语法五
android·开发语言·kotlin
移动开发者1号1 天前
嵌套滚动交互处理总结
android·kotlin
移动开发者1号1 天前
Android工程中FTP加密传输与非加密传输的深度解析
android·java·kotlin
yzpyzp2 天前
Kotlin的MutableList和ArrayList区别
android·kotlin
帅次2 天前
Flutter Container 组件详解
android·flutter·ios·小程序·kotlin·iphone·xcode
帅次2 天前
Flutter setState() 状态管理详细使用指南
android·flutter·ios·小程序·kotlin·android studio·iphone