Android

关注公众号 jb51net

关闭
首页 > 软件编程 > Android > Kotlin协程异常处理

详解Kotlin协程的异常处理机制

作者:淘淘养乐多

协程会遇到各种异常情况,比如协程被取消、协程内部发生错误、协程之间的异常传播等,这些异常情况需要我们正确地处理,否则可能会导致程序崩溃、资源泄露或者逻辑错误,本文将介绍 Kotlin 协程的异常处理机制,需要的朋友可以参考下

Kotlin 协程的异常处理

协程会遇到各种异常情况,比如协程被取消、协程内部发生错误、协程之间的异常传播等。这些异常情况需要我们正确地处理,否则可能会导致程序崩溃、资源泄露或者逻辑错误。本文将介绍 Kotlin 协程的异常处理机制,包括以下几个方面:

协程的取消

协程的取消是一种协作机制,也就是说,协程需要主动检查自己是否被取消,并在合适的时候停止执行。这样做的好处是可以避免在不安全的状态下终止协程,比如在操作共享资源或者执行不可逆操作时。协程可以通过以下几种方式来检查自己是否被取消:

下面是一个简单的例子,演示了如何使用 isActive 和 delay() 来实现可被取消的协程:

import kotlinx.coroutines.*
fun main() = runBlocking {
    // 创建一个 Job 对象
    val job = launch {
        // 在一个循环中执行一些工作
        var i = 0
        while (isActive) { // 检查协程是否被取消
            println("job: I'm working...${i++}")
            // 模拟耗时操作
            delay(500L)
        }
    }
    // 等待一段时间
    delay(1300L)
    println("main: I'm tired of waiting!")
    // 取消协程
    job.cancel()
    println("main: Now I can quit.")
}

输出结果:

job: I'm working...0
job: I'm working...1
job: I'm working...2
main: I'm tired of waiting!
main: Now I can quit.

从输出结果可以看出,在调用 job.cancel() 后,循环就停止了,并没有继续打印 "job: I'm working..."。这是因为 delay() 函数在检测到协程被取消时,抛出了 CancellationException 异常,导致协程结束。如果我们不使用 delay(),而是使用 Thread.sleep(),那么协程就不会响应取消,而是继续执行,直到循环结束。这是因为 Thread.sleep() 是一个阻塞函数,它不会检查协程的取消状态,也不会抛出任何异常。因此,我们应该尽量避免在协程中使用阻塞函数,而是使用挂起函数。

CancellationException 异常

CancellationException 是一种特殊的异常,它用于表示协程的正常取消。它继承自 IllegalStateException,但是有以下几个特点:

下面是一个例子,演示了如何在协程取消时捕获和抛出 CancellationException 异常:

import kotlinx.coroutines.*
fun main() = runBlocking {
    // 创建一个 Job 对象
    val job = launch {
        try {
            // 在一个循环中执行一些工作
            var i = 0
            while (isActive) { // 检查协程是否被取消
                println("job: I'm working...${i++}")
                // 模拟耗时操作
                delay(500L)
            }
        } catch (e: CancellationException) {
            // 捕获取消异常
            println("job: I'm cancelled, reason: ${e.message}")
        } finally {
            // 在 finally 块中执行一些操作
            println("job: I'm in the finally block")
            // 抛出取消异常
            throw CancellationException("I don't want to finish normally")
        }
    }
    // 等待一段时间
    delay(1300L)
    println("main: I'm tired of waiting!")
    // 取消协程,并传递一个原因
    job.cancel(CancellationException("Too slow"))
    println("main: Now I can quit.")
}

输出结果:

job: I'm working...0
job: I'm working...1
job: I'm working...2
main: I'm tired of waiting!
job: I'm cancelled, reason: Too slow
job: I'm in the finally block
main: Now I can quit.

从输出结果可以看出,在调用 job.cancel() 后,协程进入了 catch 语句,并打印了取消的原因。然后进入了 finally 块,并打印了一条信息。最后,在 finally 块中抛出了一个新的 CancellationException 异常,并传递了一个自定义的消息。这个异常并没有被打印或者捕获,而是被忽略了。这是因为当一个协程被取消时,它只关心第一个 CancellationException 异常,并以它作为结束的原因。后续的任何 CancellationException 异常都会被忽略。

其他异常处理手段

除了使用 try-catch 语句来处理协程中的异常外

下面是一个例子,演示了如何使用 SupervisorJob 和 CoroutineExceptionHandler 来处理协程中的异常:

import kotlinx.coroutines.*
fun main() = runBlocking {
    // 创建一个 SupervisorJob 对象
    val supervisor = SupervisorJob()
    // 创建一个 CoroutineExceptionHandler 对象
    val handler = CoroutineExceptionHandler { context, exception ->
        // 处理未捕获的异常
        println("Caught $exception in ${context[CoroutineName]}")
    }
    // 使用 supervisor 和 handler 创建一个新的作用域
    supervisorScope {
        // 在作用域内创建三个子协程
        val child1 = launch(supervisor + CoroutineName("child1")) {
            println("child1: I'm working...")
            delay(500L)
            println("child1: I'm done.")
        }
        val child2 = launch(supervisor + CoroutineName("child2")) {
            println("child2: I'm working...")
            delay(1000L)
            // 抛出一个异常
            throw ArithmeticException("Oops!")
        }
        val child3 = launch(supervisor + handler + CoroutineName("child3")) {
            println("child3: I'm working...")
            delay(1500L)
            println("child3: I'm done.")
        }
    }
    // 等待作用域结束
    println("main: The scope is over.")
}

输出结果:

child1: I'm working...
child2: I'm working...
child3: I'm working...
child1: I'm done.
Caught java.lang.ArithmeticException: Oops! in child2
child3: I'm done.
main: The scope is over.

从输出结果可以看出,在 child2 抛出异常后,并没有影响 child1 和 child3 的运行,它们都正常地完成了自己的任务。这是因为使用了 SupervisorJob 来创建作用域,使得子协程之间互不影响。同时,我们也可以看到,在 child2 抛出异常后,调用了 CoroutineExceptionHandler 来处理这个异常,并打印了相关信息。这是因为使用了 handler 来定义一个统一的异常处理函数,并将它添加到 child3 的上下文中。注意,handler 并没有添加到 child2 的上下文中,因为如果这样做,那么 child2 的异常就会被捕获并处理,而不会传播到父协程和其他兄弟协程中。这样就会打破 SupervisorJob 的语义,使得父协程和其他兄弟协程无法感知到 child2 的异常。因此,在使用 SupervisorJob 时,我们应该避免在子协程中使用 CoroutineExceptionHandler,而是在父协程或者其他兄弟协程中使用。

以上就是详解Kotlin协程的异常处理机制的详细内容,更多关于Kotlin协程异常处理的资料请关注脚本之家其它相关文章!

您可能感兴趣的文章:
阅读全文