[Kotlin Tutorials 22] 协程...
协程中的异常处理![coroutineexceptionhandl...
如果一个coroutine抛出了异常, 它将会把这个exception向上抛给它的parent, 它的parent会做以下三件事情:
取消其他所有的children.取消自己.把exception继续向上传递.这是默认的异常处理关系, 取消是双向的, child会取消parent, parent会取消所有child.
(相关资料图)
看这个代码片段:
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.RuntimeExceptionendSupervisorJob如果有一些情形, 开启了多个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的特性:
使用注意事项, 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可是不行的.
CoroutineExceptionHandlerCoroutineExceptionHandler是异常处理的最后一个机制, 此时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]单独说一下asyncasync比较特殊:
作为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时, 异常会被直接抛出.
特殊的CancellationExceptionCancellationException是特殊的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 handlingAndroid官方文档上链接的博客和视频:
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!关键词:
仓储是现代物流建设中不可缺少的一个重要环节。为了鼓励仓储行业发展,今年7月底,河南出台的17条政策支持现代物流强省建设,第一条就提到
网易新闻 2022-09-162022年8月15日,A股上市公司美诺华(代码:603538 SH)发布2022年半年度业绩报告。2022年1月1日-2022年6月30日,公司实现营业收入8 97亿元,
资本邦 2022-08-17中新经纬11月24日电 (张澍楠)虽说三百六十行,行行出状元,但行业之间的差距,仍然很大。究竟什么行业“最香”?被视为“高富帅”的金
2021-11-24新华社郑州11月23日电(记者冯大鹏)在“狗咬人”舆情发酵后,23日晚,河南安阳通报了对涉“狗咬人”事件责任单位和责任人的处理决定。
2021-11-2411月23日晚,朝阳区来广营地区清苑路第五社区华贸城7号院6号楼正式解除管控。 11月23日,华贸城7号院6号楼583户管控居民进行了第四
2021-11-2411月23日大连市新冠肺炎疫情防控总指挥部发布,大连市严格落实新冠肺炎疫情防控各项措施,至2021年11月23日24时,大连市庄河市城关街道
2021-11-24根据云南省普洱市哀牢山 "11·15 "联合指挥部通报,2021年11月23日21时50分,4名遇难人员遗体已移交其所在单位。 【编辑:叶攀】
2021-11-24
仓储是现代物流建设中不可缺少的一个重要环节。为了鼓励仓储行业发展,今年7月底,河南出台的17条政策支持现代物流强省建设,第一条就提到
网易新闻 2022-09-16
2022年8月15日,A股上市公司美诺华(代码:603538 SH)发布2022年半年度业绩报告。2022年1月1日-2022年6月30日,公司实现营业收入8 97亿元,
资本邦 2022-08-17
中新经纬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
协程中的异常处理![coroutineexceptionhandl...
财经网讯6月7日,亚厦股份发布关于实际控制人...
世界银行日前上调今年中国经济增长预期引发了...
6月3日,甘肃民族师范学院科研处负责人带领化...
赛博朋克2077快速破解怎么用?快速破解是黑客...
山西长子“羊倌”养羊20余年 带动700余户养殖户发“羊财”
上海首个“两山”实践创新基地成功创建
黄埔海关破获案值5.5亿元走私进口二手挖掘机案
绛州鼓乐搭起晋港青少年文化交流桥梁
中国援建老挝铁道职业技术学院 为老培养铁路技术人才
Copyright 2015-2022 人人仓储网版权所有 备案号:粤ICP备18023326号-36 联系邮箱:8557298@qq.com