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

一、异常的传播

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

二、打断传播

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

2.1 supervisorJob

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

//避免在只有一个子协程的时候传参使用(孙协程仍会相互取消,作用相当于普通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()

supervisorScope{
    launch{
        println("子协程1")
        throw Exception()
    }
    launch{
        println("子协程2")    //会打印
    }
}

三、CancellationException

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

//分别开启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处理。
//不会阻止异常传递,但可以定义在发生异常时的处理行为(默认情况它会打印异常堆栈)
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)
}

猜你喜欢

转载自blog.csdn.net/HugMua/article/details/132797774
今日推荐