Kotlin Coroutine - CoroutineExceptionHandler

1. Abnormal spread

  • Job cancellation and exception propagation are two-way (structured concurrency). If an exception is thrown by a coroutine without local capture and processing, the coroutine will first cancel all sub-coroutines and then cancel itself. If the exception is a CancellationException type, it will terminate upward. Propagation, if SupervisorScope() or supervisorJob is used, no matter what type of exception, upward propagation will be terminated. Otherwise, it will be passed to the root coroutine, causing all coroutines in the entire structure to be canceled.
  • Exceptions of type CancellationException are handled by the coroutine that throws them. Exceptions of type SupervisorJob are handled by the subordinate sub-coroutine of this layer (the coroutine that throws the exception or the parent coroutine, but they are both sub-coroutines of the SupervisorJob layer). In other cases, the exception is handled by the root coroutine. Coroutine processing.

2. Interrupt communication

Used when you don't want exceptions to propagate upward or affect sibling coroutines (downward propagation still exists).

2.1 supervisorJob

Internal exceptions will not cancel the parent coroutine and sibling coroutines, nor will they be affected by them. However, exceptions occurring within the internal sub-coroutines will affect each other. Failure to handle exceptions can cause the program to crash.

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

If the exception is CancellationException and its subclasses, it will not be passed upward, only the current coroutine and its subclasses will be canceled.

//分别开启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")    //会打印
    }
}

4. Exception catching

launch Exceptions thrown in the code block are thrown directly.
async The exceptions thrown in the code block are thrown only after the final consumption, that is, calling await(). Exceptions in the sub-coroutine are not affected by await(), and uncaught ones will be passed up to the root coroutine for processing. Therefore, capturing each await() separately is to avoid crashes that affect other concurrent tasks, and then capturing all async is to prevent sub-coroutine exceptions from being passed upwards and causing the program to crash (you can also put a layer of coroutineScope() outside that will throw exceptions and not be passed upwards). catch), or use CoroutineExceptionHandler.

4.1 try-catch

  • The capture coroutine builder is invalid. To capture the exception thrown in the coroutine code block : If the exception thrown in the coroutine code block is not captured and processed, it will be captured and encapsulated into a Result object in BaseContinuationImpl.resumeWith() and passed. Finally, If passed to the exception handler, it will not be thrown again, so there will be no exception to catch, that is, the builder will not throw an exception.

  • Can capture the exception of the child thread in the suspended function : try to capture the child thread is invalid, and can only capture the stack information of the current thread. The exception in the suspension function that enables the child thread can be captured in the coroutine because the underlying code of the suspension function uses reusmeWithExceptoon() to carry the exception from the child thread to the current thread and throw it. Otherwise, it will not be caught directly by throwing. Causes a permanent hang.

4.2 CoroutineExceptionHandler

  • The exception will be propagated all the way up to the root coroutine. If the root coroutine does not respond (SupervisorScope() or supervisorJob), the direct sub-coroutine will look for the CoroutineExceptionHandler in the context for processing, otherwise the UncaughtExceptionHandler will be used, so the CoroutineExceptionHandler in other sub-coroutines will not will work.
  • Conditions for use: Either exist in the context of the coroutine scope, or exist in a direct child coroutine of the root coroutine.
  • It can only handle exceptions in the current scope. For example, if a coroutine opened by another scope object is called in a code block, the exception that occurs needs to be handled by a CoroutineExceptionHandler configured in the context of the scope object.
//不会阻止异常传递,但可以定义在发生异常时的处理行为(默认情况它会打印异常堆栈)
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)
}

Guess you like

Origin blog.csdn.net/HugMua/article/details/132797774