朱涛 · Kotlin 编程第一课
朱涛
Google 认证的 Kotlin、Android 开发者专家,博客“Kotlin Jetpack 实战”作者
6717 人已学习
新⼈⾸单¥59
登录后,你可以任选4讲全文学习
课程目录
已完结/共 50 讲
朱涛 · Kotlin 编程第一课
15
15
1.0x
00:00/00:00
登录|注册

23 | 异常:try-catch居然会不起作用?坑!

你好,我是朱涛。这节课我们来学习 Kotlin 协程的异常处理。
其实到这里,我们就已经学完所有 Kotlin 协程的语法知识了。但在真正把 Kotlin 协程应用到生产环境之前,我们还需要掌握一个重要知识点,那就是异常处理。
比起 Kotlin 协程的语法知识点,协程的异常处理,其实更难掌握。在前面的课程中,我们已经了解到:协程就是互相协作的程序,协程是结构化的。正因为 Kotlin 协程有这两个特点,这就导致它的异常处理机制与我们普通的程序完全不一样。
换句话说:如果把 Java 里的那一套异常处理机制,照搬到 Kotlin 协程里来,你一定会四处碰壁。因为在普通的程序当中,你使用 try-catch 就能解决大部分的异常处理问题,但是在协程当中,根据不同的协程特性,它的异常处理策略是随之变化的。
我自己在工作中就踩过很多这方面的坑,遇到过各种匪夷所思的问题:协程无法取消、try-catch 不起作用导致线上崩溃率突然大增、软件功能错乱却追踪不到任何异常信息,等等。说实话,Kotlin 协程的普及率之所以不高,很大一部分原因也是因为它的异常处理机制太复杂了,稍有不慎就可能会掉坑里去。
那么今天这节课,我们就会来分析几个常见的协程代码模式,通过解决这些异常,我们可以总结出协程异常处理的 6 大准则。掌握了这些准则之后,你在以后遇到异常问题时,就能有所准备,也知道该怎么处理了。
确认放弃笔记?
放弃后所记笔记将不保留。
新功能上线,你的历史笔记已初始化为私密笔记,是否一键批量公开?
批量公开的笔记不会为你同步至部落
公开
同步至部落
取消
完成
0/2000
荧光笔
直线
曲线
笔记
复制
AI
  • 深入了解
  • 翻译
    • 英语
    • 中文简体
    • 中文繁体
    • 法语
    • 德语
    • 日语
    • 韩语
    • 俄语
    • 西班牙语
    • 阿拉伯语
  • 解释
  • 总结

Kotlin协程异常处理是一个复杂的技术挑战,本文通过深入分析各种场景和问题,总结出了6大准则,为读者提供了解决异常问题的有效方法。首先,协程的取消需要内部的配合,需要在协程体中加入状态判断来响应取消事件。其次,不要轻易打破协程的父子结构,否则子协程可能无法跟随父协程一起取消。文章还探讨了try-catch在Kotlin协程中的应用,指出不要直接包裹launch、async,而是将try-catch挪到协程体内部。此外,对于CancellationException的处理,需要注意捕获后是否应该重新抛出。灵活使用SupervisorJob和CoroutineExceptionHandler可以控制异常传播的范围,提供了实用的指导。总的来说,本文通过深入分析Kotlin协程异常处理的各种场景和问题,总结出了实用的准则和技巧,为读者提供了解决异常问题的有效方法。

仅可试看部分内容,如需阅读全部内容,请付费购买文章所属专栏
《朱涛 · Kotlin 编程第一课》
新⼈⾸单¥59
立即购买
登录 后留言

全部留言(13)

  • 最新
  • 精选
  • 神秘嘉Bin
    (1)Cancel依赖Cancel异常。 (2)SupervisorJob重写了childCancelled=false,导致取消不会向上和兄弟传播。 (3)异常的传播应该是先向上传播,然后都没人处理才会触发协程的CoroutineExceptionHandler,在触发全局默认的CoroutineExceptionHandler。 (4)ExceptionHandler代替try catch不合理,无法清晰的对业务异常有一个认知,不知道是哪里来的,只能通用处理;同时我认为ExceptionHandler或者作为兜底策略也是合理的,子协程对自己的业务进行异常处理,同时顶层协程有一个兜底策略,上报后需要及时让子协程进行处理;这个问题就像Java线程要不要加UnCauthedExceptionHandler【协程也可以加默认的】。

    作者回复: 很棒的总结!

    2022-03-15
    22
  • better
    老师,我实验了 CoroutineExceptionHandler ,貌似可以用在局部,还是我哪里搞错了,关键点 + 了一个 Job() 就可以了; 环境:JDK: 11 , Kotlin: 1.6.10, Kotlin coroutine-core: 1.6.0 代码如下: launch { delay(100L) launch(exceptionHandler + Job() ) { // delay(100L) 1 / 0 // Cause Exp } }

    作者回复: 因为你加了一个Job()以后,协程的父子关系就被打破了,这时候它其实就相当于顶层协程了。(请看本节课的第二张图。)

    2022-03-14
    2
    13
  • Allen
    由于 Coroutine 是结构化的,当一个子 job 出现异常会导致其他协程都停止运行,在一些场景下并不是我们想要的。我们需要使用 try catch 来将异常捕获在发生异常的地方。 如果使用 supervisorJob 来阻止异常传递的话,CoroutineExceptionHandler 又无法接收到异常,导致我们无法知道哪一个 Job由于什么原因被中止了。这时使用 try catch 可以捕获到对应的异常信息。 言而总之,try catch 在处理协程异常时,还是很有必要的。

    作者回复: 有理有据,分析的角度也很全面。

    2022-03-11
    7
  • 魏全运
    思考题: 全部用ExceptionHandler 替代try catch是不合理的,因为在实际编码过程中,我们有时需要在当前上下文合适地处理异常,比如兜底操作,重试等,并不是一味的把异常抛出去,如果都交给顶层就无法很好地处理这些case。类似于Java的UnCauthedExceptionHandler 。

    作者回复: 思路很棒,UnCauthedExceptionHandler这个类比也很恰当。

    2022-03-11
    5
  • êwěn
    思考题: 个人理解,看场景。有些业务是可以把异常当作分支处理,这种情况,handler就不适合了。

    作者回复: 很对,如果我们需要针对异常做一些特殊的业务逻辑,handler就不适合了。

    2022-03-11
    3
  • dashingqi
    请问,文章中的动画是怎么做的啊?

    作者回复: 我用PPT动画一帧一帧做的。

    2022-04-25
  • syz
    准则二例子 val job1 = Job() val pJob: Job = launch(myFixedThread) { launch(job1) {//coroutine#3} launch{//CoroutineId(4)} } 照搬的例子,测试时发现job1下的协程也停止了打印 检查发现 "coroutine#3":StandaloneCoroutine{Active}@543fc144, "coroutine#4":StandaloneCoroutine{Cancelling}@32e5773b 问题:打破协程父子结构的子协程coroutine#3(父协程是job1,当前处于Active状态)停止了打印?pJob的cancel没有影响到job1的Active状态,但是coroutine#3不再打印数据

    作者回复: 如果你完全照搬代码段3的话,应该是会得出跟文章中一样的结果的。你的代码执行不符合预期,一定是有其他的改动吧?你可以把完整代码贴出来看看。

    2022-04-15
  • jim
    代码段2: launch(Dispatchers.Default) 这里为什么必须使用Default线程池?

    作者回复: 因为协程内部有阻塞的行为:Thread.sleep(500L)

    2022-04-06
    2
  • 追梦小乐
    关于异常要写对应的异常,不要用父类的Except ion,在使用快捷键给一点代码加上try-catch后,出来的就是父类的题材,那老师我们在编写代码的时候,怎么判断是属于某一个具体的异常?!

    作者回复: 一方面是靠经验,另一方面是靠读源码,还有一方面是靠单元测试。

    2022-03-23
  • 白乾涛
    以下代码,"End" 都打印了,子 Job 也停止了,为啥程序不能停止? fun main() = runBlocking { val dispatcher = Executors.newFixedThreadPool(2) { Thread(it, "bqt") }.asCoroutineDispatcher() val pJob = launch(dispatcher) { launch { var i = 0 while (true) { delay(500) i++ println("cJob - $i") } } } delay(2000L) pJob.cancel() pJob.join() println("End") }

    作者回复: 试试把线程池改为:守护线程。

    2022-03-20
    2
收起评论
显示
设置
留言
13
收藏
沉浸
阅读
分享
手机端
快捷键
回顶部