kotlin协程async await的异常踩坑以及异常处理的正确姿势

2023-11-12

使用Kotlin来做一些异步操作相信大家都非常熟悉了,特别是结合Jetpack的一些组件,使得我们在Android开发中写异步任务非常的方便。

但是,关于在使用协程的时候,个人觉得异常处理这一块是相对来讲是需要花时间去了解的地方,因为在使用过程中还是会遇到一些小坑的,这里记录下之前遇到的坑。

踩一个使用async await时异常处理的坑

kotlin 协程的异常处理官方文档
我们先来看官方的示例:

在这里插入图片描述

可以看到,示例代码中在对 async 开启的协程进行异常捕获是在调用 await 时。看打印结果也确实是捕获到了,没什么问题。

按照这个示例代码,我们可能会很自然的就这么用了,比如在ViewModel中写了如下代码:

    fun testAsync() {
        viewModelScope.launch {
            val deferred = async {
                LogUtils.e("准备抛出异常")
                delay(1000)
                throw Exception("async 抛出了一个异常")
            }
            try {
                deferred.await()
            } catch (e: Exception) {
                LogUtils.e("在 await 处捕获到 async的异常了")
            }
            LogUtils.e("后续代码继续执行")
        }
    }

按照官方给的示例代码来看,async的异常是在调用await的时候才会抛出。那我们在调用await的时候进行try catch,就可以捕获到async抛出的异常,并且程序不会崩溃。
我们来实际运行看一下:

在这里插入图片描述

这里我再进入异常处理的时候调用了一下上面的代码,可以发现,app居然崩溃了。下面我们来看看打印的日志
在这里插入图片描述

可以看到,我们明明在await的时候已经捕获到异常了,为什么App还会崩溃呢?

如果你仔细看官方示例中的注释,会发现这么两个单词 root coroutine
在这里插入图片描述

直接说结果:

当async作为根协程时,被封装到deferred对象中的异常才会在调用await时抛出。
如果async作为一个子协程时,那么异常并不会等到调用await时抛出,而是立刻抛出异常

这也就是为什么我们明明在await的地方进行try catch,但是程序仍然会崩溃的原因。
因为被立刻抛出的异常没有被得到处理,所以只能崩溃了。

只不过官方文档并没有明确的说明作为子协程的情况异常是怎样的,所以,我之前在这里是踩过坑的。

这里你可能就有疑问了,不对啊,看日志明明就是await的时候才抛出的异常,你怎么能说是立刻抛出的呢,怎么证明呢。
我只能说:
在这里插入图片描述
很简单,我们在await之前加个延时,看看日志打印就知道了。
代码如下,跟上面一样,直接加了个延时。

    fun testAsync() {
        viewModelScope.launch {
            val deferred = async {
                LogUtils.e("准备抛出异常")
                delay(1000)
                throw Exception("async 抛出了一个异常")
            }
            /*加个延时 主要是验证异常是不是在await的时候抛出*/
            delay(2000)
            try {
                deferred.await()
            } catch (e: Exception) {
                LogUtils.e("在 await 处捕获到 async的异常了")
            }
            LogUtils.e("后续代码继续执行")
        }
    }

再来看看运行情况,可以看到,await的try catch的日志就没有机会打印了。为啥呢,因为上面的代码已经出异常了,程序都崩了,就没有等2秒继续执行的机会了。上面的代码会打印是因为执行的很快,所以才会给你一种错觉是以为还是在await的时候抛出的异常。
在这里插入图片描述

下面我们再来验证下作为顶级作用域时是不是真的是在调用await的时候抛出的异常:
同样的处理方式,只不过此时async变成了顶级作用域

    fun testTopAsync() {
        /*顶级作用域的async*/
        val deferred = viewModelScope.async {
            LogUtils.e("准备抛出异常")
            delay(1000)
            throw Exception("async 抛出了一个异常")
        }

        viewModelScope.launch {
            /*加个延时 主要是验证异常是不是在await的时候抛出*/
            delay(2000)        
            try {
                deferred.await()
            } catch (e: Exception) {
                LogUtils.e("在 await 处捕获到 async的异常了")
            }
            LogUtils.e("后续代码继续执行")

        }
    }

可以看到,确实是在2秒后的await时才捕获到异常,app也没有崩溃。

在这里插入图片描述


kotlin协程异常处理的正确姿势

这里只是我自己觉得正确的处理方式

首先,在使用协程时,一定要加 coroutineexceptionhandler

这个是对当前协程作用域内的异常做一个兜底,也就是作用域中未被捕获到的异常最终交给coroutineexceptionhandler处理,这样,至少能保证你的App不会崩溃

我们来看下面的代码:

    /*异常处理*/
    private val exceptionHandler = CoroutineExceptionHandler { coroutineContext, throwable ->
        LogUtils.e("exceptionHandler:${throwable}")
    }

    fun testAsync() {
        viewModelScope.launch(exceptionHandler) {
            val deferred = async {
                LogUtils.e("准备抛出异常")
                delay(1000)
                throw Exception("async 抛出了一个异常")
            }
            /*加个延时 主要是验证异常是不是在await的时候抛出*/
            delay(2000)
            try {
                deferred.await()
            } catch (e: Exception) {
                LogUtils.e("在 await 处捕获到 async的异常了")
            }
            LogUtils.e("后续代码继续执行")
        }
    }

还是之前的代码,只不过加了个 CoroutineExceptionHandler ,这个CoroutineExceptionHandler 就不多啰嗦了,官网文档介绍的也比较详细了。

然后看一下运行效果:

在这里插入图片描述

可以看到
async抛出的未被捕获到的异常就被
CoroutineExceptionHandler 给处理掉了,这样即使你的协程块内有一些忘处理的异常抛出,也不会导致App崩溃

至于加了CoroutineExceptionHandler 后 CoroutineScope和 supervisorScope的区别这里就不多介绍了,官网介绍的都比较清楚。

来简单总结下:

  • 对每个协程内部进行try cath 是最保险的做法,简单粗暴,虽然麻烦,但是不会出错,稳得一批
  • 不管有没有做try catch处理,都一定要在根作用域加上CoroutineExceptionHandler ,以防万一
  • 使用async await时要注意下作用域的问题,以免出现跟预期不一样的结果

好了,本文就是这样,希望能帮到你


如果你觉得本文对你有帮助,麻烦动动手指顶一下,可以帮助到更多的开发者,如果文中有什么错误的地方,还望指正,转载请注明转自喻志强的博客 ,谢谢!

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

kotlin协程async await的异常踩坑以及异常处理的正确姿势 的相关文章

  • Android 从命令行停止模拟器

    这个问题与如何通过命令行关闭Android模拟器 https stackoverflow com questions 5912403 how to shut down android emulator via cmd 但是 在尝试第一个答案
  • 在Android Studio中更改项目主题?

    我使用浅色主题创建了一些项目 现在我想将其更改为深色 但我不知道该怎么做 顺便说一句 我不是问如何在代码中做到这一点 只是问如何更改项目的默认主题 在 AndroidManifest xml 的 application 标签下 您可以设置您
  • onBackPressed 隐藏 不破坏 Activity

    我知道如何取消后退按键 以便活动 主窗口保持可见 public void onBackPressed return 我的目标是隐藏该活动 但是 在没有完成它的情况下 您如何在 onBackPressed 事件中做到这一点 即我想达到 onP
  • 无法将类型“System.IO.Stream”隐式转换为“Java.IO.InputStream”

    我提到了一些类似的问题 但没有一个涉及IO 当我使用时 我在java中使用了相同的代码Eclipse 那次就成功了 但现在我尝试在中使用这段代码Mono for Android C 它不起作用 我正在尝试运行此代码来创建一个InputStr
  • FCM onMessageReceived 应用程序运行时返回空白消息和标题

    正如您在标题中所写 当应用程序关闭时 它运行良好 并且onMessageReceived获取消息正文和标题 但如果应用程序处于前台模式 运行模式 则可以发送通知 但没有消息和标题 请问该怎么办 代码 Override public void
  • Android 自定义警报对话框中的 OnClickListener

    我是一个自学成才的初学者 感谢耐心 谢谢 在 Eclipse 中 我使用自己的 xml 文件 custom dialog 创建了一个自定义警报对话框 称为 usernamealert 如果用户尚未输入用户名 即 username lengt
  • Android构建gradle太慢(依赖解析)

    我使用 Android Studio 我当前的版本 1 5 已经有 2 年了 一切都很好 但是当我下载 Canary 2 1 p5 时 一切都出了问题 每次我想创建一个新项目或打开一个项目或同步或导入新的库或依赖项时 gradle 的构建时
  • 如何在 Android 中使列表视图中的项目不可点击

    如何使列表视图中的项目无法单击 我在列表视图中获得了主题和项目 但主题和项目的视图相同 项目可单击 但主题不可单击 如何实现这一目标 该列表看起来像 Topic item Topic item item 话题 单击able false 不起
  • 如何动态改变描边颜色?

    抱歉我的语言不通 我是法国人 我需要改变stroke color形状的 我有同样的问题描述here https stackoverflow com questions 16775891 how to change solid color f
  • 三星 Galaxy Note II - 逗号作为数字键盘小数分隔符

    我正在尝试使用逗号作为三星 Galaxy Note II 上数字键盘的小数分隔符 我已经在其他设备 Moto X rooted Samsung GS4 上测试了我的应用程序 如果我从设备的系统设置中更改语言 它们的小键盘具有正确的分隔符 三
  • Android:对文本后面的图像使用alignBaseline

    下面是一个 TextView 后跟一个包含在relativelayout中的imageview 我试图使图像的底部与文本的基线对齐 当我对图像使用alignBaseline时 引用TextView 图像的顶部与文本的基线对齐 而不是底部 我
  • 从后台返回时,Android 片段被放置在前一个片段之上

    在我的 MainActivity 中 我有三个片段 还有一个 BottomNavigationView 来处理要显示的片段 这是我的 MainActivity 的 OnCreate 中的内容 fragmentManager beginTra
  • Android中BaseColumns有什么用

    实现一个类有什么用BaseColumns在安卓中 The BaseColumns http developer android com reference android provider BaseColumns html接口提供了非常常见
  • 查看寻呼机 - 使用静态变量以编程方式滑动到下一页

    我想在我的 ViewPager 中以编程方式制作幻灯片 我的问题是 滑动事件是由放置在 ViewPager 保存的片段内部的按钮调用的 我知道代码 viewpager setCurrentItem int index 现在我的想法是使 Vi
  • 为什么 cordova.file.documentsDirectory 为空?

    我正在尝试使用 cordova plugin file transfer 在http ngcordova com docs plugins fileTransfer http ngcordova com docs plugins fileT
  • 不同 Android 设备上 box2D 中出现奇怪的“口吃”

    我正在用 C 同时开发引擎和游戏 并使用 box2D 作为物理后端 我正在不同的 Android 设备上进行测试 在三分之二的设备上 游戏运行良好 物理效果也很好 然而 在我的 Galaxy Tab 10 1 上 我偶尔会遇到某种 口吃 的
  • 处理 Android 锁屏上的音量变化?

    我想做的是 能够在 android 4 4 上的锁屏上捕获音量增大 减小按钮操作 Google Cast 设计清单 https developers google com cast docs design checklist sender
  • 如何将 Android Instrumentation 测试推送到模拟器/设备?

    我正在尝试使用 Ubuntu 9 04 中的命令行 shell 在 Android 模拟器上运行 Webkit 布局测试 adb s emulator 5554 shell am instrument w com android dumpr
  • Android Facebook sdk 3.5 分享对话框

    您好 我正在为 android sdk 3 5 实现 facebook 共享对话框 但是我按照指南没有取得任何成功 FacebookDialog shareDialog new FacebookDialog ShareDialogBuild
  • Android 5.0 Lollipop 中屏幕固定关闭时如何收到通知?

    我有一个在后台运行的应用程序 并在手机上发生特定事件时启动活动 我发现在 Android 5 0 中 当用户使用另一个应用程序打开屏幕固定时 startActivity intent 调用将被完全忽略 我的应用程序不知道该活动尚未启动 因此

随机推荐

  • MinStack 和MaxStack

    leetcode链接 包含min函数的stack 分析 利用一个LinkedList 链表存储数据 类似于链stack 还有数组stack 采用ArrayList存储 关于如何查找最小元素的情况 思路一 双stack stack 保存正常的
  • git stash的用法

    首先 git stash的含义是将修改的代码先暂存起来 让本地仓库回到最后一次提交时的状态 便于代码的更新管理 主要避免修改文件与最新代码的冲突 最近项目中遇到一些文件修改了 暂时不想提交 就想到了使用stash命令 首先 可以将自己想提交
  • 学习笔记整理:网络应用技术-运输层(1)

    以下内容为个人的学习笔记整理 如有错误 请指出 谢谢 一 课前预习 1 数据交换有哪几种方式 电路交换 报文交换 分组交换 2 运输层实现的通信是什么之间的通信 两个网络应用程序之间的通信 3 运输层所说的端口有什么作用 什么是套接字 端口
  • 详解基于tensorflow实现对cifar100的识别,准确率达到65%附完整代码(涉及vggnet,resnet,,loss图像处理,图像增强,BN)

    文章目录 一 介绍cifar 数据集 二 resnet网络简介 a 网络结构图 b 使用resnet进行炼丹 c 第一次炼丹 d 第二次炼丹 完整代码 jupyter notebook 三 vggnet网络简介 a vggnet结构图 b
  • vue-cli+express前后端分离项目跨域问题解决

    1 express后端项目中使用命令npm i cors S安装cors 并在app js文件中引入cors 写下如下几行代码 var cors require cors 跨域 app use cors origin http localh
  • Java集合-HashMap1.8也会发生死循环

    在网上搜资料时候然后发现网上都说1 7版本的HashMap会发生死链也就是死循环 但是在HashMap中也会产生死循环 接下来直接看代码吧 代码 类名字我忘记改了这是我以前看park时候弄的但是这不重要 当你运行 public class
  • [第16课]统计:诸方差公式

    Start 观看可汗视频 本节课 可汗老师对原始方差公式进行推导 得出如下更简洁的公式 2 i
  • MapReduce基础知识(个人总结)

    声明 1 本文为我的个人复习总结 并非那种从零基础开始普及知识 内容详细全面 言辞官方的文章 2 由于是个人总结 所以用最精简的话语来写文章 3 若有错误不当之处 请指出 Writable类型 Java类型 Hadoop Writable类
  • 构建无服务器 ChatGPT 支持的简历助手 - 基础(二)

    之前我们制作了一个简洁的小命令行工具 可以用来帮助我们构建更好的简历 现在我们将该应用程序部署到云中 我将使用 AWS CDK 因为我喜欢它 而且它使此类事情变得相对简单 如果愿意 您当然可以将这些说明改编为 Terraform 首先让我们
  • CDH6.3修改主机IP

    这里写自定义目录标题 修改ip 停服务 进入mysql修改元数据ip 修改各主机的ip 分别修改各主机的ip 修改所有hadoop集群机器中的cloudera scm agent的配置文件 重启服务 修改ip 开始是在公司使用桥接模式 回到
  • 根据XML文件在原始图片上画矩形框

    根据XML文件在原始图片上画矩形框 输入参数 xml src XML文件路径 img src 原始图片路径 代码 def markImage xml src img src root ET parse xml src getroot cou
  • Eclipse 下载与安装(2022超详细)

    一 下载jdk 去官网可以下载 小编把64位已下载好的放网盘 自行下载 链接 https pan baidu com s 1A0 iWtdvYfwepTXzqXWfMA 提取码 cnqz 1 下载好之后自己选择安装目录安装即可 2 配置环境
  • 留学申请计算机硕士个人陈述,计算机专业“个人陈述”样本

    Computer Science Personal Statement Computing and its applications have always fascinated me and for this reason I have
  • iText官方教程

    如果要导出PDF文件 首选当然是iText 从网上也找了些资料 有很多 也有些乱 自己做了个例子 感觉还是很浅 深入的内容还是研究不透 下载了官方的jar包 本来想看看 里面的Demo 结果只有api文档 资料还真是不好找 今天发现官方还是
  • 第1章 Python概述 课后习题参考答案

    一 单选题 1 以下选项中说法不正确的是 答案为D A 解释是将源代码逐条转换成目标代码并同时运行的过程 B 编译是将源代码转换成目标代码的过程 C Python语言是解释型语言 兼有编译功能 D 静态语言采用解释方式执行 脚本语言采用编译
  • jenkins自动化脚本整理

    发布jar文件 规范化Jenkins 编译服务器编译目录设定为 编译脚本目录 bin 发布脚本目录 deploy 编译代码目录 source 1 项目代码目录 创建source dmmclist txt文件 kc tmplt tmpltsv
  • STM32 电机教程 33 - 无刷电机无感控制快速实现

    前言 上一节 STM32 电机教程 32 基于ST X CUBE SPN7 无刷无感电机库的电机驱动实现 给大家分享了ST的官方的无刷电机无感控制实现方案 基于NUCLEO F103RB和X NUCLEO IHM07M1 3SH开发板 并给
  • R-字符串

    字符串 文本数据存储在字符向量中 或字符数组中 虽然这比较少见 字符向量中的每个元素都是字符串 在R中 字符串 是个常用的非正式术语 因为正式的 字符向量元素 读起来相当拗口 文本的基本单位是字符向量 这意味着大部分字符串处理函数也能用于字
  • jQuery筛选器

    div div ul li li li li ul
  • kotlin协程async await的异常踩坑以及异常处理的正确姿势

    使用Kotlin来做一些异步操作相信大家都非常熟悉了 特别是结合Jetpack的一些组件 使得我们在Android开发中写异步任务非常的方便 但是 关于在使用协程的时候 个人觉得异常处理这一块是相对来讲是需要花时间去了解的地方 因为在使用过