androidkotlin协程基本入门2(代码片段)

史大拿 史大拿     2023-03-03     761

关键词:

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-始终和父协程使用同一线程

官方文档介绍

先来看一个简单的例子:

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

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

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

1.

  1. -Dkotlinx.coroutines.debug

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

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

例如这样:

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

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

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

再来看一段代码:

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

withContext 与launch 调度的区别:

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

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

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

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

可以看出,可以切换,我们以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为例

可以看出,继承关系为:

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

最终都是 CoroutineContext 的子类!

完整代码

CoroutineName 协程名字

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

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

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

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

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

完整代码

CoroutineStart 协程启动模式

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

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

官方参考

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

CoroutineStart.LAZY

首先来看一段代码:

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

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

CoroutineStart.ATOMIC

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

先来看正常效果:

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

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

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

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

再来看一段代码:

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

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

再换一种挂起点方式

也还是同样的结果.

Coroutine.UNDISPATCHED

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

首先还是看默认状态

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

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

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

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

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

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

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

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

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

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

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

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

完整代码

CoroutineException 协程异常捕获

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

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

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

CoroutineExceptionHandler => AbstractCoroutineContextElement => Element => CoroutineContext

最终继承自 CoroutineContext

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

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

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

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

Job配合CoroutineHandler 异常捕获

先来看一段简单的代码:

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

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

然后协程2就不会执行了

SupervisorJob()

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

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

Job与 SupervisorJob 的区别也很明显

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

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

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

coroutineScope

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

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

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

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

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

superiverScope

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

效果很简单

这里主要要分清楚

SuperiverScope() 和 superiverScope 是不一样的

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

async捕获异常

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

例如这样:

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

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

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

如何让 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的话,肯定知道这一步是在做什么

完整代码

下一篇预告:

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

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

androidkotlin协程理解挂起,恢复以及job(代码片段)

androidkotlin协程(三)理解挂起,恢复以及job前言:通过上两篇的基础入门,相信大家对协程api已经有了一个基本的影响,本篇开始尝试理解挂起于恢复.本篇不涉及源码,通过很多应用案例,来理解挂起于恢复!协程执行流程理解还是老套路,... 查看详情

androidkotlin协程协程间的通信(代码片段)

androidkotlin协程(四)协程间的通信学完本篇你将会了解到:channelproduceactorselect先来通过上一篇的简单案例回顾一下挂起于恢复:funmain()valwaitTime=measureTimeMillisrunBlocking<Unit>println("mainstart")//1//调度前launchprintln("launch1start... 查看详情

androidkotlin协程协程间的通信(代码片段)

学完本篇你将会了解到:channelproduceactorselect先来通过上一篇的简单案例回顾一下挂起于恢复:funmain()valwaitTime=measureTimeMillisrunBlocking<Unit>println("mainstart")//1//调度前launchprintln("launch1start")//2//调度后(执行前)delay(1000)... 查看详情

androidkotlin回顾10.如何启动协程(代码片段)

1.launch启动协程funmain()=runBlockinglaunchdelay(1000L)println("World!")println("Hello")funmain()GlobalScope.launchdelay(1000L)println("World!")println("Hello")Thread.s 查看详情

androidkotlin协程suspend与continuation(代码片段)

androidkotlin协程(五)suspend与continuation通过本篇你将学会:suspendCoroutinesuspendCancellableCoroutinesuspend与continuationsuspendCoroutine第一次看到这玩意的时候肯定有点身体不适,先不用管这个东西是什么,目前为止只需要知道suspendCoroutine是一个函... 查看详情

androidkotlin协程源码浅析(代码片段)

androidkotlin协程(六)源码浅析前言:kotlin协程源码十分庞大,本篇只能吧我理解的源码聊一聊,不会特别深入研究,只会浅浅的看看表层.本来计划协程系列是10篇左右,后续是flow热流冷流之类的,冷流操作符之类的应该不会在写了,flow当作... 查看详情

最全androidkotlin入门教程(kotlin入门指南高级kotlin强化实战kotlin协程进阶实战)

Kotlin是一种新型的静态类型编程语言,有超过60%的专业Android开发者在使用,它有助于提高工作效率、开发者满意度和代码安全性。不仅可以减少常见代码错误,还可以轻松集成到现有应用中。目前在安卓开发中,... 查看详情

androidkotlin之coroutine(协程)详解(代码片段)

协程是一种并发设计模式,您可以在Android平台上使用它来简化异步执行的代码。在Android上,协程有助于管理长时间运行的任务,如果管理不当,这些任务可能会阻塞主线程并导致应用无响应。协程的优点:轻... 查看详情

一个有趣的小例子,带你入门协程模块-asyncio(代码片段)

一个有趣的小例子,带你入门协程模块-asyncio上篇文章写了关于yieldfrom的用法,简单的了解异步模式,【https://www.cnblogs.com/c-x-a/p/10106031.html】这次让我们通过一个有趣例子带大家了解asyncio基本使用。目标效果图基本原理1.通过不停的... 查看详情

php协程入门详解(代码片段)

...切换也是有一定开销的。 这时为了解决以上问题,"协程"(coroutine)的概念就产生了。你可以将协程理解为更轻量级的线程。这种线程叫做“用户空间线程“。协程,有下面两个特点 查看详情

androidkotlin协程coroutine

1.协程coroutine协程是一种并发设计模式,在Kotlin中使用协程可以简化异步执行的代码,把异步回调代码同步化。协程,其实就是相互协作的子程序,多个子程序之间通过一定的机制相互关联、协作地完成某项任务... 查看详情

python协程&异步编程(asyncio)入门介绍(代码片段)

...await装饰的函数,查询资料后了解到这种函数是基于协程的异步函数。这类编程方式称为异步编程,常用在IO较频繁的系统中,如:Tornadoweb框架、文件下载、网络爬虫等应用。协程能够在IO等待时间就去切换执行其... 查看详情

kotlin协程万字协程一篇完成kotlin协程进阶(代码片段)

kotlin协程进阶协程简介一、协程的基本使用1.1、runBlocking启动1.2、GlobalScope.launch启动1.3、GlobalScope.async启动1.4、三种启动方式的说明二、Coroutine源码解析2.1、CoroutineContext2.2、Job源码2.3、Job的常用函数2.4、SupervisorJob三、suspend关键... 查看详情

python入门自学进阶——11-协程(代码片段)

协程,又叫做微线程,纤程。Coroutine。协程是一种用户态的轻量级线程。协程拥有自己的寄存器上下文和栈,调度切换时,将寄存器上下文和栈保存,在切换回来时,恢复寄存器山下文和栈。协程能保留上... 查看详情

zookeeper----zookeeper基本入门(代码片段)

...模型的特点三、节点的分类四、安装Docker安装五、客户端基本指令六、节点监听机制watch七、ZooKeeperJavaAPI操作1、Curator介绍2、CuratorAPI常用操作2.1建立连接2.2添加节点2.3删除节点2.4修改节点2.5查询节点2.6Watch事件监听八、集群架构... 查看详情

unity零基础到入门☀️|小万字教程对unity中的协程❤️全面解析+实战演练❤️(代码片段)

📢博客主页:https://blog.csdn.net/zhangay1998📢欢迎点赞👍收藏⭐留言📝如有错误敬请指正!📢本文由呆呆敲代码的小Y原创,首发于CSDN🙉📢未来很长,值得我们全力奔赴更美好的生活✨&#... 查看详情

javascript入门(代码片段)

JavaScript入门(2)文章目录JavaScript入门(2)一.JS函数1.基本语法2.调用3.传参return二.JSif...else语句1.基本语法2.条件并列3.例子:三个数比大小三.JS对象1.引入2.属性3.方法this关键词4.实例四.JS循环1.while循环基本语法来试一个输入密码... 查看详情

kafaka基础快速入门(代码片段)

Kafaka基本入门文章目录Kafaka基本入门一基本认识1.1消息中间件(消息队列)1.2常用消息中间件1.3通信协议1.4基本术语二kafaka的基本介绍2.1概述2.2消息系统介绍2.3点对点消息传递模式2.4发布-订阅消息传递模式三Kafka中的术... 查看详情