[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")}

打印出:

startchild 1child 2child 2 throws exceptionCoroutine 1 got cancelled!CoroutineExceptionHandler got java.lang.RuntimeExceptionend
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")}

输出:

startchild 1child 2child 2 throws exceptionCoroutineExceptionHandler got java.lang.RuntimeExceptionfinish child 1end

尽管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 exceptionChildren are cancelled, but exception is not handled until all children terminateThe first child finished its non cancellable blockCoroutineExceptionHandler 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 causeCoroutineExceptionHandler got java.io.IOException

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

Further Reading

官方文档:

Coroutine exceptions handling

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

Exceptions in coroutinesKotlinConf 2019: Coroutines! Gotta catch "em all! by Florina Muntenescu & Manuel Vivo

其他:

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

关键词:

郑州3个区仓储供不应求 仓储超2000万平方

仓储是现代物流建设中不可缺少的一个重要环节。为了鼓励仓储行业发展,今年7月底,河南出台的17条政策支持现代物流强省建设,第一条就提到

网易新闻 2022-09-16

美诺华2022年中报 上半年实现营收8.97亿元 同比增长29.74%

2022年8月15日,A股上市公司美诺华(代码:603538 SH)发布2022年半年度业绩报告。2022年1月1日-2022年6月30日,公司实现营业收入8 97亿元,

资本邦 2022-08-17

各行业工资单出炉!IT类最赚钱,还有这些钱景喜人

  中新经纬11月24日电 (张澍楠)虽说三百六十行,行行出状元,但行业之间的差距,仍然很大。究竟什么行业“最香”?被视为“高富帅”的金

2021-11-24

“狗咬人”事件当事人被撤职 多名干部被问责

  新华社郑州11月23日电(记者冯大鹏)在“狗咬人”舆情发酵后,23日晚,河南安阳通报了对涉“狗咬人”事件责任单位和责任人的处理决定。 

2021-11-24

北京朝阳区来广营华贸城7号院6号楼解除管控

  11月23日晚,朝阳区来广营地区清苑路第五社区华贸城7号院6号楼正式解除管控。  11月23日,华贸城7号院6号楼583户管控居民进行了第四

2021-11-24

大连市将4个中风险地区调整为低风险地区

  11月23日大连市新冠肺炎疫情防控总指挥部发布,大连市严格落实新冠肺炎疫情防控各项措施,至2021年11月23日24时,大连市庄河市城关街道

2021-11-24

云南哀牢山4名遇难地质队员遗体已移交其所在单位

  根据云南省普洱市哀牢山 "11·15 "联合指挥部通报,2021年11月23日21时50分,4名遇难人员遗体已移交其所在单位。 【编辑:叶攀】

2021-11-24

郑州3个区仓储供不应求 仓储超2000万平方

仓储是现代物流建设中不可缺少的一个重要环节。为了鼓励仓储行业发展,今年7月底,河南出台的17条政策支持现代物流强省建设,第一条就提到

网易新闻 2022-09-16

美诺华2022年中报 上半年实现营收8.97亿元 同比增长29.74%

2022年8月15日,A股上市公司美诺华(代码:603538 SH)发布2022年半年度业绩报告。2022年1月1日-2022年6月30日,公司实现营业收入8 97亿元,

资本邦 2022-08-17

各行业工资单出炉!IT类最赚钱,还有这些钱景喜人

  中新经纬11月24日电 (张澍楠)虽说三百六十行,行行出状元,但行业之间的差距,仍然很大。究竟什么行业“最香”?被视为“高富帅”的金

2021-11-24

第三届拉萨市旅游行业服务技能大赛决赛举行

  中新网拉萨11月23日电 (记者 冉文娟)第三届拉萨市旅游行业服务技能大赛决赛11月23日精彩举行。百余名选手经过层层选拔,经历初赛、网

2021-11-24

聚焦解决人兽冲突 东北虎豹国家公园启动专项行动

  中新网长春11月23日电 (郭佳 吴林锡)东北虎豹国家公园23日全面启动2021-2022年今冬明春清山清套·打击乱捕滥猎专项行动。该行动旨在

2021-11-24

呼吸治疗专家浦其斌逝世 曾多次参与公共卫生事件救治

2021-11-24

穗港澳职工交流基地挂牌 将带动更多穗港澳青年建设湾区

  中新网广州11月23日电 (蔡敏婕 罗瑞雄)“穗港澳职工交流服务基地”23日在广州挂牌。广州市总工会主席唐航浩称,设立穗港澳职工交流服

2021-11-24
x 广告
x 广告
x 广告

Copyright   2015-2022 人人仓储网版权所有  备案号:粤ICP备18023326号-36   联系邮箱:8557298@qq.com