android kotlin 协程(二) 基本入门2

news2024/9/23 15:25:54

android kotlin 协程(二)

config:

  • system: macOS

  • android studio: 2022.1.1 Electric Eel

  • gradle: gradle-7.5-bin.zip

  • android build gradle: 7.1.0

  • Kotlin coroutine core: 1.6.4

tips:前面几篇全都是协程的基本使用,没有源码,等后面对协程有个基本理解之后,才会简单的分析一下源码!

上一篇(android kotlin coroutine 基本入门)

看完本篇你能学会什么:

  • CoroutineDispatcher // 协程调度器 用来切换线程

  • CoroutineName // 协程名字

  • CoroutineStart // 协程启动模式

  • CoroutineException // launch / async 捕获异常

  • GlobalCoroutineException // 全局捕获异常

CoroutineDispatcher 协程调度器

定义: 根据名字也可以看出来, 协程调度器, 主要用来切换线程,主要有4种

  • Dispatchers.Main - 使用此调度程序可在 Android 主线程上运行协程。
  • Dispatchers.IO - 此调度程序经过了专门优化,适合在主线程之外执行磁盘或网络 I/O。示例包括使用 Room 组件、从文件中读取数据或向文件中写入数据,以及运行任何网络操作。
  • Dispatchers.Default - 此调度程序经过了专门优化,适合在主线程之外执行占用大量 CPU 资源的工作。用例示例包括对列表排序和解析 JSON。
  • Dispatchers.Unconfined-始终和父协程使用同一线程

官方文档介绍

先来看一个简单的例子:

image-20230210100808380

这行代码的意思是开启一个协程,他的作用域在子线程上

可以看出,只要设置DIspatchers.IO 就可以切换线程

tips: 这里我使用的是协程调试才可以打印出协程编号

1.image-20230210100955680

  1. -Dkotlinx.coroutines.debug

    image-20230210101023349

使用协程DIspatcher切换线程的时候,需要注意的是,子协程如果调度了,就使用调度后的线程,如果没有调度,始终保持和父协程相同的线程

这里的调度就是指的是否有DIspatcher.XXX

例如这样:

image-20230210101633250

对于coroutine#4,他会跟随 coroutine#3 的线程

coroutine#3 会 跟随 coroutine#2 的线程

coroutine#2 有自身的调度器IO,所以全部都是IO线程

再来看一段代码:

image-20230210103107799

withContext() 是用来切换线程,这里切换到主线程,但是输出的结果并没有切换到主线程

withContext{} 与launch{} 调度的区别:

  • withContext 在原有协程上切换线程
  • launch 创建一个新的协程来切换线程

这里我感觉是kotlin对JVM支持还不够

因为本身JVM平台就没有Main线程,Main线程是对与Android平台的

所以我们将这段代码拿到android平台试一下

image-20230210102725891

可以看出,可以切换,我们以android平台为主!

这里需要注意的是:

JVM平台上没有Dispatcher.Main, 因为Main只是针对android的,所以如果想要在JVM平台上切换Main线程,

需要添加:

implementation (“org.jetbrains.kotlinx:kotlinx-coroutines-test:1.6.4”)

并且在dispatcher.Main之前调用 Dispatchers.setMain(Dispatchers.Unconfined)

gitHub issues

现在我们知道了通过Dispatcher.XXX 就可以切换线程, 那么Dispatcher.XXX是什么呢? 这里以Dispatcher.IO为例

image-20230210105911786

可以看出,继承关系为:

Dispatcher.IO = DefaultIoScheduler => ExecutorCoroutineDispatcher => CoroutineDispatcher => AbstractCoroutineContextElement => Element => CoroutineContext

最终都是 CoroutineContext 的子类!

完整代码

CoroutineName 协程名字

**定义:**协程名字, 子协程会继承父协程的名字, 如果协程种有自己的名字,那么就优先使用自己的

image-20230210152847116

这块代码比较简单,就不废话了

image-20230210162156439

可以看出,CoroutineName也是CoroutineContext的子类, 如果说

现在我们现在想要切换到子线程上我们该怎么做?

通过刚才的代码,我们知道DIspatcher.XXX 其本质就是CoroutineContext, 那么我们就可以通过内置的操作符重载来实现两个功能的同时操作

image-20230210162509633

完整代码

CoroutineStart 协程启动模式

定义: coroutineStart 用来控制协程调度器,以及协程的执行时机等

  • CoroutineStart.DEFAULT: 立即根据其上下文安排协程执行;
  • CoroutineStart.LAZY: 懒加载,不会立即执行,只有调用的时候才会执行
  • CoroutineStart.ATOMIC: 常配合Job#cancel()来使用, 如果协程体中有新的挂起点,调用Job#cancel()时 取消挂起点之后的代码,否则全部取消
  • CoroutineStart.UnDISPATCHED: 不进行任何调度,包括线程切换等, 线程状态会跟随父协程保持一致

官方参考

CoroutineStart.DEFAULT 我相信不用过多赘述, 默认就是这个,直接从 CoroutineStart.LAZY开始

CoroutineStart.LAZY

首先来看一段代码:

CoroutineStart-Lazy

可以通过这段代码发现, 其余的协程都执行了,只有采用CoroutineStart.LAZY的协程没有执行,并且runBlocking 会一直等待他执行

那么只需要调用Job#start() 或者 job#join() 即可

image-20230210175105779

CoroutineStart.ATOMIC

tips:该属性目前还在试验阶段

先来看正常效果:

image-20230210194428793

在这段代码中,我们开启了一个协程,然后立即cancel了,协程中的代码没有执行

如果改成 CoroutineStart.ATOMIC 会发生什么情况呢?

image-20230210194547704

可以惊奇的发现,居然取消协程没有作用!

那么这个CoroutineStart.ATOMIC到底有什么用呢?

再来看一段代码:

image-20230213104724981

可以看出, CoroutineStart.ATOMIC 会将挂起点之后的代码给cancel掉,

即使这里delay很久,也会立即cancel

再换一种挂起点方式

image-20230213104918005

也还是同样的结果.

Coroutine.UNDISPATCHED

定义: 不进行任何调度,包括线程切换等, 线程状态会跟随父协程保持一致

首先还是看默认状态

image-20230213133925157

注意:这里代码会首先执行:1.main start2. main end

这里有一个调度的概念,比较抽象:

image-20230213134104808

协程始终都是异步执行的,kotlin协程的底层也是线程, kotlin协程说白了就是一个线程框架,

所以创建协程的时候,其实就是创建了一个线程, 使用线程的时候,我们会通过Thread#start() 告诉JVM我们有一个任务需要执行,

然后JVM去分配,最后JVM去执行

这里调度的大致逻辑和线程类似

只不过协程可以轻易的实现2个线程之前切换,切换回来的过程在协程中我们叫它恢复

这里扯的有点远,先来看本篇的内容 😃

我们来看看 Coroutine.UNDISPATCHED有什么作用

image-20230213143444851

可以看出,一旦使用了这种启动模式, 就没有了调度的概念,即使是切换线程(withContext)也无济于事

跟随父协程线程状态而变化

image-20230213145126614

说实话,这种启动模式我认为比较鸡肋,和不写这个协程好像也没有很大的区别

完整代码

CoroutineException 协程异常捕获

重点: 协程异常捕获必须放在最顶层的协程作用域上

最简单的我们通过try catch 来捕获,这种办法就不说了,

首先我们来看看 coroutineException的继承关系

image-20230213164458469

CoroutineExceptionHandler => AbstractCoroutineContextElement => Element => CoroutineContext

最终继承自 CoroutineContext

到目前为止,我们知道了 coroutineContext有4个有用的子类

  • Job 用来控制协程生命周期
  • CoroutineDispatcher 协程调度器,用来切换线程
  • CoroutineName 写成名字
  • CoroutineException 协程异常捕获

首先我们来分析 CoroutineScope#launch 异常捕获

捕获异常之前先说一个秘密: Job不仅可以用来控制协程生命周期,还可以用不同的Job 来控制协程的异常捕获

Job配合CoroutineHandler 异常捕获

先来看一段简单的代码:

tip: 如果不写Job 默认就是Job()

image-20230213165529549

可以看出,目前的状态是协程1出现错误之后,就会反馈给CoroutineExcetionHandler

然后协程2就不会执行了

SupervisorJob()

假如有一个场景,我们需要某个子协程出现问题就出现问题,不应该影响到其他的子协程执行,那么我们就可以用 SupervisorJob()

**SupervisorJob()**的特点就是:如果某个子协程出现问题不会影响兄弟协程

image-20230213165914619

Job与 SupervisorJob 的区别也很明显

  • Job 某个协程出现问题,会直接影响兄弟协程,兄弟协程不会执行
  • SupervisorJob 某个协程出现问题,不会影响兄弟协程.

如果现在场景变一下,现在换成了子协程中出现问题,来看看效果

image-20230213170604284

可以看出, 子协程2并没有执行 这是默认效果,若在子协程中开启多个子协程,其实建议写法是这样的

coroutineScope{}

image-20230213171536117

为什么要这么写呢? 明明我不写效果就一样,还得写这玩意,不是闲的没事么

我感觉,作用主要就是统一代码,传递CoroutineScope 例如这样

image-20230213172133040

正常在实际开发中如果吧代码全写到一坨,应该会遭到同行鄙视 :]

现在场景又调整了, 刚才是子协程出现问题立即终止子协程的兄弟协程

现在调整成了: 某个子协程出现问题,不影响子协程的兄弟协程,就想 SupervisorJob() 类型

superiverScope{}

那就请出了我们的superiverScope{} 作用域

image-20230213172559111

效果很简单

这里主要要分清楚

SuperiverScope() 和 superiverScope{} 是不一样的

  • SuperiverScope() 是用来控制兄弟协程异常的,并且他是一个
  • superiverScope{} 是用来控制子协程的兄弟协程的,他是一个函数

async捕获异常

重点: async使用 CoroutineExceptionHandler 是捕获不到异常的

例如这样:

image-20230213173928389

async 的异常在 Deferred#await()中, 还记得上一篇中我们聊过 Deferred#await()这个方法会获取到async{} 中的返回结果

如果我们想要捕获async{} 中的异常,我们只需要try{} catch{} await即可,例如这样写

image-20230213174309411

async 也可以配合 SupervisorJob() 达到子协程出现问题,不影响兄弟协程执行,例如这样:

image-20230213191750604

如何让 CoroutineExceptionHandler 监听到async的异常,本质是监听不到的,

但是,我们知道了deferred#await() 会抛出异常,那么我们可以套一层 launch{} 这样一来就可以达到我们想要的效果

suspend fun main() {
    val exceptionHandler = CoroutineExceptionHandler { _, throwable ->
        printlnThread("catch 到了 $throwable")
    }
    val customScope =
        CoroutineScope(SupervisorJob() + CoroutineName("自定义协程") + Dispatchers.IO + exceptionHandler)

    val deferred1 = customScope.async {
        printlnThread("子协程 1 start")
        throw KotlinNullPointerException(" ============= 出错拉 1")
        "协程1执行完成"
    }

    val deferred2 = customScope.async {
        printlnThread("子协程 2 start")
        "协程2执行完成"
    }
    val deferred3 = customScope.async {
        printlnThread("子协程 3 start")
        throw KotlinNullPointerException(" ============= 出错拉 3")
        "协程3执行完成"
    }

    customScope.launch {
        supervisorScope {
            launch {
               val result =  deferred1.await()
                println("协程1 result:$result")
            }
            launch {
                val result =  deferred2.await()
                println("协程2 result:$result")
            }
            launch {
                val result =  deferred3.await()
                println("协程3 result:$result")
            }
        }
    }.join()
}

结果为:

子协程 3 start:	 thread:DefaultDispatcher-worker-2 @自定义协程#3
子协程 2 start:	 thread:DefaultDispatcher-worker-3 @自定义协程#2
子协程 1 start:	 thread:DefaultDispatcher-worker-1 @自定义协程#1
协程2 result:协程2执行完成
catch 到了 kotlin.KotlinNullPointerException:  ============= 出错拉 3:	 thread:DefaultDispatcher-worker-2 @自定义协程#7
catch 到了 kotlin.KotlinNullPointerException:  ============= 出错拉 1:	 thread:DefaultDispatcher-worker-1 @自定义协程#5

协程捕获异常,最终要的一点就是,协程中的异常会一直向上传递,如果想要 使用 CoroutineExceptionHandler,监听到异常,那么就必须将 CoroutineExceptionHandler 配置到最顶级的coroutineScope

完整代码

GlobalCoroutineException 全局异常捕获

需要在本地配置一个捕获监听:

resources/META-INF/services/kotlinx.coroutines.CoroutineExceptionHandler

就和APT类似,如果你玩过APT的话,肯定知道这一步是在做什么

image-20230213194624115

完整代码

下一篇预告:

  • 协程执行流程 [入门理解挂起与恢复]
  • delay() 与 Thread#sleep() 区别

原创不易,您的点赞就是对我最大的支持!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/342758.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

(一)初识Streamlit(附安装)

本入门指南介绍Streamlit的工作原理、如何在您首选的操作系统上安装Streamlit,以及如何创建第一个Streamlit应用程序! 1 安装 1.1 先决条件 Python 3.7 – Python 3.11 **注:我这里使用的是anaconda的虚拟环境,用pycharm编写代…

JavaWeb--MavenMybatis基础

JavaWeb--Maven&Mybatis基础1 Maven1.1 Maven简介1.1.1 Maven模型1.1.2 仓库1.2 Maven基本使用1.2.1 Maven 常用命令1.2.2 Maven 生命周期1.3 IDEA使用Maven1.3.1 IDEA配置Maven环境1.3.2 Maven 坐标详解1.3.3 IDEA 创建 Maven项目1.3.4 IDEA 导入 Maven项目1.4 依赖管理1.…

UVa 11212 Editing a Book 编辑书稿 IDA* Iterative Deepening A Star 迭代加深搜剪枝

题目链接&#xff1a;Editing a Book 题目描述&#xff1a; 给定nnn个(1<n<10)1<n<10)1<n<10)数字&#xff0c;数字分别是1,2,3,...,n1, 2, 3, ...,n1,2,3,...,n&#xff0c;但是顺序是打乱的&#xff0c;你可以选择一个索引区间的数字进行剪切操作。问最少进…

即便考分很好也不予录取的研究生复试红线,都是原则性问题

在浙大研究生招生录取政策文件中有这么一句话&#xff1a;坚持“按需招生、全面衡量、择优录取、宁缺毋滥”的原则&#xff0c;以提高人才选拔质量为核心&#xff0c;在确保安全性、公平性和科学性的基础上&#xff0c;做到统筹兼顾、精准施策、严格管理。字字体现出研究生招生…

保姆级手把手教你如何使用HTTP远程连接Docker?

为什么要远程访问Docker? 可以使用http协议&#xff0c;获取json格式数据&#xff0c;很方便使用代码控制镜像&#xff0c;so easy 怎么配置才可以远程访问呢&#xff1f; 新建或修改这个文件&#xff1a;如果没有就新增哦~ /etc/systemd/system/docker.service.d/overrid…

Flink中核心重点总结

目录 1. 算子链 1.1. 一对一&#xff08;One-to-one&#xff0c; forwarding&#xff09; 1.2. 重分区&#xff08;Redistributing&#xff09; 1.3. 为什么有算子链 2. 物理分区&#xff08;Physical Partitioning&#xff09; 2.1. 什么是分区 2.2. 随机分区&#xff…

【Python学习笔记】30.Python3 命名空间和作用域

前言 本章介绍Python的命名空间和作用域。 命名空间 先看看官方文档的一段话&#xff1a; A namespace is a mapping from names to objects.Most namespaces are currently implemented as Python dictionaries。 命名空间(Namespace)是从名称到对象的映射&#xff0c;大…

在中国程序员工作是青春饭吗?

上个月公司告诉我毕业了。 我打开boss直聘&#xff0c;一溜溜的外包公司和我打招呼。 我寻思我说不定啥时候就离开深圳了&#xff0c;外包不外包也无所谓钱到位就行。&#xff08;大公司学历不够格也进不去&#xff09; 结果华为、平安的外包告诉我&#xff0c;不好意思呀&a…

配置MyBatis Plus 的分页查询功能

配置MyBatis Plus 的分页查询功能一. 回顾Mysql分页查询二. 配置MyBatis Plus 分页功能2.1 配置分页拦截器2.2 进行分页查询三. 开启MyBatis Plus的运行日志一. 回顾Mysql分页查询 limit 是MySQL当中特有的&#xff01;其他数据库没有&#xff01;不通用&#xff1b;limit 是M…

ES6新增特性总结

目录 let和const命令 模板字符串 扩展运算符 解构赋值 对象解构 数组解构 扩展的函数 带参数默认值的函数 剩余参数表示不定参 箭头函数 扩展的对象 直接写入变量和函数&#xff0c;作为对象的属性和方法 新增Object.is()/Object.assign()/Object.keys/Object.val…

内网渗透(二十一)之Windows协议认证和密码抓取-Golden Ticket黄金票据制作原理及利用方式

系列文章第一章节之基础知识篇 内网渗透(一)之基础知识-内网渗透介绍和概述 内网渗透(二)之基础知识-工作组介绍 内网渗透(三)之基础知识-域环境的介绍和优点 内网渗透(四)之基础知识-搭建域环境 内网渗透(五)之基础知识-Active Directory活动目录介绍和使用 内网渗透(六)之基…

UI自动化测试是什么?什么项目适合做UI自动化测试

1. 页面对象设计模式的优势(1) 创建可以跨多个测试用例共享的代码(2) 减少代码的重复性(3) 如果界面需要维护&#xff0c;只需要修改一个地方&#xff0c;修改以及维护的成本减少2. 每个目录结构表达的意思(1) Base:基础层&#xff0c;是用来编写定位元素(2) Common&#xff1a…

[chatGPT] 如何通过JNI在Android上显示实时视频流

目录背景正文layout xmljavaCjava总结一&#xff1a;追问&#xff1a;CC总结二&#xff1a;答疑解惑C画蛇添足 视频不显示黑屏最后感叹科技的更新速度&#xff0c;真的程序员都可能会被替代&#xff0c;下一个时代最大的问题应该是劳动力过剩&#xff0c;导致社会性结构改变&am…

Hudi-集成 Hive

集成 Hive Hudi 源表对应一份 HDFS 数据&#xff0c;通过 Spark&#xff0c;Flink 组件或者 Hudi CLI&#xff0c;可以将 Hudi 表的数据映射为 *Hive 外部表*&#xff0c;基于该外部表&#xff0c; Hive可以方便的进行实时视图&#xff0c;读优化视图以及增量视图的查询。 集…

【软件相关】文献管理工具——Zotero

文章目录0 前期教程1 前言2 一些说明3 下载安装4 功能一&#xff1a;插入文献引用格式5 功能二&#xff1a;从网页下载文献pdf和题录6 功能三&#xff1a;数据多平台同步7 功能四&#xff1a;通过DOI添加条目及添加订阅8 安装xpi插件9 功能五&#xff1a;智能识别中英文文献10 …

The Number Of ThreadPoolExecutor

序言整理下Java 线程池中线程数量如何设置的依据巨人肩膀:https://blog.csdn.net/weilaizhixing007/article/details/125955693https://blog.csdn.net/yuyan_jia/article/details/120298564#:~:text%E4%B8%80%E4%B8%AA%E7%BA%BF%E7%A8%8B%E6%B1%A0%E5%A4%84%E7%90%86%E8%AE%A1,…

MongoDB--》文档查询的详细具体操作

目录 统计查询 分页列表查询 排序查询 正则的复杂条件查询 比较查询 包含查询 条件连接查询 统计查询 统计查询使用count()方法&#xff0c;其语法格式如下&#xff1a; db.collection.count(query,options) ParameterTypeDescriptionquerydocument查询选择条件optio…

int和Integer有什么区别?

第7讲 | int和Integer有什么区别&#xff1f; Java 虽然号称是面向对象的语言&#xff0c;但是原始数据类型仍然是重要的组成元素&#xff0c;所以在面试中&#xff0c;经常考察原始数据类型和包装类等 Java 语言特性。 今天我要问你的问题是&#xff0c;int 和 Integer 有什么…

JUC并发编程 Ⅱ -- 共享模型之管程(下)

文章目录wait notifywait / notify的原理API 介绍sleep与wait辨析优雅地使用wait/notify保护性暂停模式超时版 GuardedObjectjoin原理多任务版GuardedObject生产者-消费者模式定义实现Park & Unpark基本使用特点原理重新理解线程状态转换线程的活跃性死锁定位死锁活锁饥饿R…

vs2019 winform安装包,安装完成后默认启动

vs2019 winform安装包&#xff0c;安装完成后默认启动在要打包的项目中&#xff0c;新建安装类选中打包项目&#xff0c;右键&#xff0c;进入文件系统![在这里插入图片描述](https://img-blog.csdnimg.cn/62647c550ffa4d489f1d19f19bbd99b1.png)选中application folder 右键&a…