[Kotlin Tutorials 22] 协程中的异常处理

这篇具有很好参考价值的文章主要介绍了[Kotlin Tutorials 22] 协程中的异常处理。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

协程中的异常处理

[Kotlin Tutorials 22] 协程中的异常处理

Parent-Child关系

如果一个coroutine抛出了异常, 它将会把这个exception向上抛给它的parent, 它的parent会做以下三件事情:

  • 取消其他所有的children.
  • 取消自己.
  • 把exception继续向上传递.

这是默认的异常处理关系, 取消是双向的, child会取消parent, parent会取消所有child.

catch不住的exception

看这个代码片段:

fun main() {
    val scope = CoroutineScope(Job())
    try {
        scope.launch {
            throw RuntimeException()
        }
    } catch (e: Exception) {
        println("Caught: $e")
    }

    Thread.sleep(100)
}

这里的异常catch不住了.
会直接让main函数的主进程崩掉.

这是因为和普通的异常处理机制不同, coroutine中未被处理的异常并不是直接抛出, 而是按照job hierarchy向上传递给parent.

如果把try放在launch里面还行.

默认的异常处理

默认情况下, child发生异常, parent和其他child也会被取消.

fun main() {
    println("start")
    val exceptionHandler = CoroutineExceptionHandler { _, exception ->
        println("CoroutineExceptionHandler got $exception")
    }
    val scope = CoroutineScope(Job() + exceptionHandler)

    scope.launch {
        println("child 1")
        delay(1000)
        println("finish child 1")
    }.invokeOnCompletion { throwable ->
        if (throwable is CancellationException) {
            println("Coroutine 1 got cancelled!")
        }
    }

    scope.launch {
        println("child 2")
        delay(100)
        println("child 2 throws exception")
        throw RuntimeException()
    }

    Thread.sleep(2000)
    println("end")
}

打印出:

start
child 1
child 2
child 2 throws exception
Coroutine 1 got cancelled!
CoroutineExceptionHandler got java.lang.RuntimeException
end

SupervisorJob

如果有一些情形, 开启了多个child job, 但是却不想因为其中一个的失败而取消其他, 怎么办? 用SupervisorJob.

比如:

val uiScope = CoroutineScope(SupervisorJob())

如果你用的是scope builder, 那么用supervisorScope.

SupervisorJob改造上面的例子:

fun main() {
    println("start")
    val exceptionHandler = CoroutineExceptionHandler { _, exception ->
        println("CoroutineExceptionHandler got $exception")
    }
    val scope = CoroutineScope(SupervisorJob() + exceptionHandler)

    scope.launch {
        println("child 1")
        delay(1000)
        println("finish child 1")
    }.invokeOnCompletion { throwable ->
        if (throwable is CancellationException) {
            println("Coroutine 1 got cancelled!")
        }
    }

    scope.launch {
        println("child 2")
        delay(100)
        println("child 2 throws exception")
        throw RuntimeException()
    }
    Thread.sleep(2000)
    println("end")
}

输出:

start
child 1
child 2
child 2 throws exception
CoroutineExceptionHandler got java.lang.RuntimeException
finish child 1
end

尽管coroutine 2抛出了异常, 另一个coroutine还是做完了自己的工作.

SupervisorJob的特点

SupervisorJob把取消变成了单向的, 只能从上到下传递, 只能parent取消child, 反之不能取消.
这样既顾及到了由于生命周期的结束而需要的正常取消, 又避免了由于单个的child失败而取消所有.

viewModelScope的context就是用了SupervisorJob() + Dispatchers.Main.immediate.

除了把取消变为单向的, supervisorScope也会和coroutineScope一样等待所有child执行结束.

supervisorScope中直接启动的coroutine是顶级coroutine.
顶级coroutine的特性:

  • 可以加exception handler.
  • 自己处理exception.
    比如上面的例子中coroutine child 2可以直接加exception handler.

使用注意事项, SupervisorJob只有两种写法:

  • 作为CoroutineScope的参数传入: CoroutineScope(SupervisorJob()).
  • 使用supervisorScope方法.

把Job作为coroutine builder(比如launch)的参数传入是错误的做法, 不起作用, 因为一个新的coroutine总会assign一个新的Job.

异常处理的办法

try-catch

和普通的异常处理一样, 我们可以用try-catch, 只是注意要在coroutine里面:

fun main() {
    val scope = CoroutineScope(Job())
    scope.launch {
        try {
            throw RuntimeException()
        } catch (e: Exception) {
            println("Caught: $e")
        }
    }

    Thread.sleep(100)
}

这样就能打印出:

Caught: java.lang.RuntimeException

对于launch, try要包住整块.
对于async, try要包住await语句.

scope function: coroutineScope()

coroutineScope会把其中未处理的exception抛出来.

相比较于这段代码中catch不到的exception:

fun main() {
    val scope = CoroutineScope(Job())
    scope.launch {
        try {
            launch {
                throw RuntimeException()
            }
        } catch (e: Exception) {
            println("Caught: $e")
        }
    }
    Thread.sleep(100)
}

没走到catch里, 仍然是主进程崩溃.

这个exception是可以catch到的:

fun main() {
    val scope = CoroutineScope(Job())
    scope.launch {
        try {
            coroutineScope {
                launch {
                    throw RuntimeException()
                }
            }
        } catch (e: Exception) {
            println("Caught: $e")
        }
    }

    Thread.sleep(100)
}

打印出:

Caught: java.lang.RuntimeException

因为这里coroutineScope把异常又重新抛出来了.

注意这里换成supervisorScope可是不行的.

CoroutineExceptionHandler

CoroutineExceptionHandler是异常处理的最后一个机制, 此时coroutine已经结束了, 在这里的处理通常是报告log, 展示错误等.
如果不加exception handler那么unhandled exception会进一步往外抛, 如果最后都没人处理, 那么可能造成进程崩溃.

CoroutineExceptionHandler需要加在root coroutine上.

这是因为child coroutines会把异常处理代理到它们的parent, 后者继续代理到自己的parent, 一直到root.
所以对于非root的coroutine来说, 即便指定了CoroutineExceptionHandler也没有用, 因为异常不会传到它.

两个例外:

  • async的异常在Deferred对象中, CoroutineExceptionHandler也没有任何作用.
  • supervision scope下的coroutine不会向上传递exception, 所以CoroutineExceptionHandler不用加在root上, 每个coroutine都可以加, 单独处理.

通过这个例子可以看出另一个特性: CoroutineExceptionHandler只有当所有child都结束之后才会处理异常信息.

@OptIn(DelicateCoroutinesApi::class)
fun main() = runBlocking {
    val handler = CoroutineExceptionHandler { _, exception -> 
        println("CoroutineExceptionHandler got $exception") 
    }
    val job = GlobalScope.launch(handler) {
        launch { // the first child
            try {
                delay(Long.MAX_VALUE)
            } finally {
                withContext(NonCancellable) {
                    println("Children are cancelled, but exception is not handled until all children terminate")
                    delay(100)
                    println("The first child finished its non cancellable block")
                }
            }
        }
        launch { // the second child
            delay(10)
            println("Second child throws an exception")
            throw ArithmeticException()
        }
    }
    job.join()
}

输出:

Second child throws an exception
Children are cancelled, but exception is not handled until all children terminate
The first child finished its non cancellable block
CoroutineExceptionHandler got java.lang.ArithmeticException

如果多个child都抛出异常, 只有第一个被handler处理, 其他都在exception.suppressed字段里.

fun main() = runBlocking {
    val handler = CoroutineExceptionHandler { _, exception ->
        println("CoroutineExceptionHandler got $exception with suppressed ${exception.suppressed.contentToString()}")
    }
    val job = GlobalScope.launch(handler) {
        launch {
            try {
                delay(Long.MAX_VALUE) // it gets cancelled when another sibling fails with IOException
            } finally {
                throw ArithmeticException() // the second exception
            }
        }
        launch {
            delay(100)
            throw IOException() // the first exception
        }
        delay(Long.MAX_VALUE)
    }
    job.join()
}

输出:

CoroutineExceptionHandler got java.io.IOException with suppressed [java.lang.ArithmeticException]

单独说一下async

async比较特殊:

  • 作为top coroutine时, 在await的时候try-catch异常.
  • 如果是非top coroutine, async块里的异常会被立即抛出.

例子:

fun main() {
    val scope = CoroutineScope(SupervisorJob())
    val deferred = scope.async {
        throw RuntimeException("RuntimeException in async coroutine")
    }

    scope.launch {
        try {
            deferred.await()
        } catch (e: Exception) {
            println("Caught: $e")
        }
    }

    Thread.sleep(100)
}

这里由于用了SupervisorJob, 所以async是top coroutine.

fun main() {

    val coroutineExceptionHandler = CoroutineExceptionHandler { coroutineContext, exception ->
        println("Handle $exception in CoroutineExceptionHandler")
    }

    val topLevelScope = CoroutineScope(SupervisorJob() + coroutineExceptionHandler)
    topLevelScope.launch {
        async {
            throw RuntimeException("RuntimeException in async coroutine")
        }
    }
    Thread.sleep(100)
}

当它不是top coroutine时, 异常会被直接抛出.

特殊的CancellationException

CancellationException是特殊的exception, 会被异常处理机制忽略, 即便抛出也不会向上传递, 所以不会取消它的parent.
但是CancellationException不能被catch, 如果它不被抛出, 其实协程没有被成功cancel, 还会继续执行.

CancellationException的透明特性:
如果CancellationException是由内部的其他异常引起的, 它会向上传递, 并且把原始的那个异常传递上去.

@OptIn(DelicateCoroutinesApi::class)
fun main() = runBlocking {
    val handler = CoroutineExceptionHandler { _, exception ->
        println("CoroutineExceptionHandler got $exception")
    }
    val job = GlobalScope.launch(handler) {
        val inner = launch { // all this stack of coroutines will get cancelled
            launch {
                launch {
                    throw IOException() // the original exception
                }
            }
        }
        try {
            inner.join()
        } catch (e: CancellationException) {
            println("Rethrowing CancellationException with original cause")
            throw e // cancellation exception is rethrown, yet the original IOException gets to the handler
        }
    }
    job.join()
}

输出:

Rethrowing CancellationException with original cause
CoroutineExceptionHandler got java.io.IOException

这里Handler拿到的是最原始的IOException.

Further Reading

官方文档:

  • Coroutine exceptions handling

Android官方文档上链接的博客和视频:

  • Exceptions in coroutines
  • KotlinConf 2019: Coroutines! Gotta catch 'em all! by Florina Muntenescu & Manuel Vivo

其他:文章来源地址https://www.toymoban.com/news/detail-475343.html

  • Kotlin Coroutines and Flow - Use Cases on Android
  • Why exception handling with Kotlin Coroutines is so hard and how to successfully master it!

到了这里,关于[Kotlin Tutorials 22] 协程中的异常处理的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • Kotlin Android中错误及异常处理最佳实践

    Kotlin在Android开发中的错误处理机制以及其优势 Kotlin具有强大的错误处理功能 :Kotlin提供了强大的错误处理功能,使处理错误变得简洁而直接。这个特性帮助开发人员快速识别和解决错误,减少了调试代码所需的时间。 Kotlin的错误处理特性 :Kotlin具有一些错误处理特性,如

    2024年02月07日
    浏览(39)
  • 【kotlin 协程】万字协程 一篇完成kotlin 协程进阶

    Kotlin 中的协程提供了一种全新处理并发的方式,可以在 Android 平台上使用它来简化异步执行的代码。协程是从 Kotlin 1.3 版本开始引入,但这一概念在编程世界诞生的黎明之际就有了,最早使用协程的编程语言可以追溯到 1967 年的 Simula 语言。 在过去几年间,协程这个概念发展

    2024年02月07日
    浏览(51)
  • FastAPI学习-22.response 异常处理 HTTPException

    某些情况下,需要向客户端返回错误提示。 这里所谓的客户端包括前端浏览器、其他应用程序、物联网设备等。 需要向客户端返回错误提示的场景主要如下: 客户端没有执行操作的权限 客户端没有访问资源的权限 客户端要访问的项目不存在 等等 … 遇到这些情况时,通常

    2024年02月07日
    浏览(34)
  • Unity 中的 async/await:优雅处理异步任务与协程

    内容将会持续更新,有错误的地方欢迎指正,谢谢!   Unity 中的 async/await:优雅处理异步任务与协程Coroutine       TechX 坚持将创新的科技带给世界! 拥有更好的学习体验 —— 不断努力,不断进步,不断探索 TechX —— 心探索、心进取! 助力快速掌握 async/await 异步等待 为初

    2024年02月06日
    浏览(49)
  • Kotlin 协程一 —— 协程 Coroutine

    1.1.1基本定义 进程 进程是一个具有一定独立功能的程序在一个数据集上的一次动态执行的过程,是操作系统进行资源分配和调度的一个独立单位,是应用程序运行的载体。 进程是资源分配的最小单位,在单核CPU中,同一时刻只有一个程序在内存中被CPU调用运行。 线程 基本的

    2024年02月05日
    浏览(48)
  • kotlin语法进阶 - 协程(一)协程基础

    协程并不是一个新的概念,而是一个非常老的概念,很多语言都支持协程,建议去浏览器去了解一下协程的历史和基本概念,这里我们只讲一下kotlin中的协程的作用。 从代码实现角度来看:kotlin协程底层是用线程实现的,是一个封装完善供开发者使用的线程框架。kotlin的一个

    2024年02月09日
    浏览(45)
  • Kotlin协程-从一到多

    上一篇文章,我介绍了Kotlin协程的创建,使用,协作等内容。本篇将引入更多的使用场景,继续带你走进协程世界。 常用编程语言都会内置对同一类型不同对象的数据集表示,我们通常称之为容器类。不同的容器类适用于不同的使用场景。Kotlin的 Flow 就是在异步计算的需求下

    2024年02月09日
    浏览(55)
  • Kotlin协程-从理论到实战

    上一篇文章从理论上对Kotlin协程进行了部分说明,本文将在上一篇的基础上,从实战出发,继续协程之旅。 在Kotlin中,要想使用协程,首先需要使用协程创建器创建,但还有个前提——协程作用域( CoroutineScope )。在早期的Kotlin实现中,协程创建器是一等函数,也就是说我们随

    2024年02月09日
    浏览(42)
  • Android Kotlin 协程初探

    维基百科:协程,英文Coroutine [kəru’tin] (可入厅),是计算机程序的一类组件,推广了协作式多任务的子程序,允许执行被挂起与被恢复。 作为Google钦定的Android开发首选语言Kotlin,协程并不是 Kotlin 提出来的新概念,目前有协程概念的编程语言有Lua语言、Python语言、Go语言

    2024年02月08日
    浏览(45)
  • Kotlin协程学习之-02

    协程的基本使用 GlobalScope.launch 生命周期与进程一致,且无法取消 runBlocking 会阻塞线程,一般在测试阶段可以使用 val coroutineScope = CoroutineScope(context) coroutineScope.launch 通过context参数去管理和控制协程的生命周期 用法 val coroutineScope = CoroutineScope(context) coroutineScope.launch(Dispatche

    2024年01月22日
    浏览(43)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包