Kotlin 协程 - 生命周期 Job

一、概念

对于每一个由协程构建器开启的协程,都会返回一个 Job 实例用来管理协程的生命周期。launch()直接返回 Job实现,async() 返回的 Deferred 实现了 Job接口。

Job public fun start(): Boolean

public fun cancel(cause: CancellationException? = null)

取消 Job 会抛异常,默认可空,也可以自定义,job.cancel(CancellationException("取消"))。它不会立马就被取消,先进入 cancelling。协程作用域和协程上下文的扩展函数cancel()底层都是调用的它。

public suspend fun join()

挂起当前协程,直到 Job 完成。

public suspend fun Job.cancelAndJoin()

挂起当前协程,直到 Job 取消完成。

public fun invokeOnCompletion(handler: CompletionHandler): DisposableHandle

Job结束后调用该回调,不管是cancelled还是competed都会回调。

Deferred

public suspend fun await(): T

挂起当前协程,直到 Deferred 完成。

Await.kt

joinAll( )

public suspend fun joinAll(vararg jobs: Job): Unit = jobs.forEach { it.join() }

挂起当前协程,直到传入的 Job 都执行完。

public suspend fun Collection<Job>.joinAll(): Unit = forEach { it.join() }

挂起当前协程,直到集合中的 Job 都执行完。

awaitAll( )

public suspend fun <T> awaitAll(vararg deferreds: Deferred<T>): List<T>

挂起当前协程,直到传入的 Deferred 都执行完。

public suspend fun <T> Collection<Deferred<T>>.awaitAll(): List<T>

挂起当前协程,直到集合中的 Deferred  都执行完。

二、生命周期

         如果Job是通过协程构建器创建的,Active就是协程主体运行时的状态,在这个状态下我们可以启动子协程。一般协程都是在Active状态下启动,只有那些延迟启动的才会以New状态启动。

        当Job完成时,会进入Completing状态等待所有子协程完成,然后进入Compelted状态。

        如果Job在Active或Completing状态下取消或者异常,会进入到Cancelling状态供我们做一些资源释放等工作,然后进入到Cancelled状态。

没有直接的生命周期函数可供调用,而是使用以下三个属性去做判断:

Job的状态/函数判断isActiveisCompletedisCancelled
New 新创建(optional initial state)falsefalsefalse
Active 活跃(default initial state)truefalsefalse
Completing 完成中(transient state)truefalsefalse
Cancelling 取消中(transient state)falsefalsetrue
Cancelled 已取消(final state)falsetruetrue
Compeleted 已完成(final state)falsetruefalse

三、协程的取消

        Java 线程其实没有提供任何机制来安全地终止线程,Thread 类提供了一个 interrupt() 方法用于中断线程的执行,并不意味着立即停止目标线程正在进行的工作,而只是传递了请求中断的消息,然后由线程在下一个合适的时机中断自己。

        仅仅终止线程是一个糟糕的方案,协程提供了一个 cancel() 函数来取消Job,但并不是一定能取消。协程的取消是协作的,一段协程代码必须协作才能被取消。所有 kotlinx.coroutines 中的挂起函数都是可被取消的,它们检查协程的取消,并在取消时抛出 CancellationException。如果协程正在执行计算任务,并且没有检查取消的话,那么它是不能被取消的。

3.1 取消的原理

public interface Job : CoroutineContext.Element {//通过序列保存了所有子Job的引用,所以具有父子层级结构public val children: Sequence<Job>
}//子Job接口
public interface ChildJob : Job {//提供了父Job取消自己的函数public fun parentCancelled(parentJob: ParentJob)
}//父Job接口
public interface ParentJob : Job {//提供了获取子Job被取消原因的函数public fun getChildJobCancellationCause(): CancellationException
}//Job的实现类,同时实现了ChildJob和ParentJob,说明一个Job对象既可以是父Job也可以是子Job
public open class JobSupport constructor(active: Boolean) : Job, ChildJob, ParentJob, SelectClause0 {internal var parentHandle: ChildHandle?   //当前协程的父协程帮助类,当前协程作为子协程
}

3.2 取消的状态

挂起函数(挂起点)会在执行的时候检查协程的状态,其它情况需要手动调用 job.isAlive 或 job.ensureActive() 来检查。

  • 运行出错或者调用cancel()后该Job会在遇到第一个挂起点开始取消并抛出CancellationException异常:
    • 先处于Cancelling状态,没有挂起点或检查措施便不会响应取消操作直至代码块执行完毕。才能继续执行其它,否则会存在其它协程并发执行。
    • 手动调用join()或遇到代码中的第一个挂起点,协程才会真正被取消,再处于Cancelled状态。推荐使用cancelAndJoin()简化调用。
  • 一旦该Job被取消,该Job下的子job也会一并取消,但父Job兄弟Job不受影响,该Job不能再用作任何新Job父Job(不能开启新协程)。

3.3 取消的异常处理

协程通过抛出一个 CancellationException异常 来取消 Job。cancel() 可以传参使用不同的异常来指定原因,需要是 CancellationException 的子类才能取消协程。该异常不会导致父协程或兄弟协程的取消,可以使用 try-catch-finally 去捕获处理释放资源,推荐使用标准函数 use() 会自动关闭资源。

suspend fun main() = runBlocking {//没有继承父协程的上下文,有自己的作用域,因此 runBlocking 不会等待 GlobalScope 执行完再结束。val job = GlobalScope.launch {try {//耗时操作}catch (e:Exception){//处理异常}finally{//释放资源}}delay(1000)  //让job运行一下再取消
//    job.cancel()    //抛异常 JobCancellationException
//    job.join()    //挂起函数,这样就会等 GlobalScope 取消完再继续执行job.cancelAndJoin() //简写
}

3.4 无法直接取消的情况(CPU密集型、没有挂起点)

由于调用cancel()操作后Job会处于Cancelling状态,此时只需判断Job是否处于活跃状态于便可以响应cancel()操作。

  • CPU密集型任务无法直接被cancel()取消,因为直接取消会丢失临时计算数据。可以通过对Job状态的判断来响应cancel()操作。
  • Job的取消发生在挂起点上,没有挂起点便不会响应cancel()操作,当我们使用协程却没有调用任何挂起函数的时候(做阻塞操作、神经网络学习)便会发生这种情况。

isActive

加在判断里

public val CoroutineScope.isActive: Boolean
    get() = coroutineContext[Job]?.isActive ?: true

判断Job是否处于活跃状态(尚未完成且尚未取消)。

ensureActive()

写在函数里

public fun CoroutineScope.ensureActive(): Unit = coroutineContext.ensureActive()

返回coroutineContext扩展函数,调用Job的函数,最终调用的是 !isActive,Job处于非活跃状态就报错CancelllationException。

yield()

不至于抢占太多线程让其它协程拿不到执行权

public suspend fun yield(): Unit

会检查所在协程的状态,如果已经取消则报错 CancellationException,此外会尝试让出线程执行权。

suspend fun main() = runBlocking {val job = launch(Dispatchers.Default) {    //该协程中无挂起点while (isActive) {   //判断出false便会取消ensureActive()              //检测出false便会取消yield()                     //不至于因为任务太抢占资源导致其它协程拿不到线程执行权println("CPU密集任务")}}delay(1000)    //让job运行一会儿后再取消println("等完")job.cancelAndJoin() //cancel()操作会将 isActive = falseprintln("结束")
}

3.5 一定无法取消的情况

由于我们可以捕获CancellationException异常,在 Job 真正结束前可以做一些事情,由于 Job 响应 cancel() 后已经处于 Cancelling状态,此时启动一个新协程(会被忽略)或者调用挂起函数(会抛异常CancellationException)是无法被执行的。

  • 方式①:指定协程上下文为NonCancellable来得到一个常驻Job不响应 cancel()操作。
  • 方式②:使用invokeOnCompletion()函数,当 Job 处于Cancelled状态Compeleted状态时会执行回调。形参it是一个异常,没有异常值为null,协程被取消值为 CancellationException。
withContext(NonCancellable){ //不会响应取消
}
job.invodeOnCompletion{//回调代码
}

3.6 自定义挂起函数定义取消的回调

详见回调函数改挂起函数

//定义
suspend fun getResource():StudentBean = suspendCancellableCoroutine{ continuation ->request(object : ICallBack{override fun onSuccess(data:String){continuation.resume(data)}override fun onFailure(exception:Throwable){continuation.resumeWithException(exception)}
})    //定义协程取消时应该做的操作continuation.invokeOnCancellation{ //TODO... }
}//使用
suspend main() = runBlocking{try{viewModelScope.launch{val bean = getResource()}}catch(e : Exception){e.printStackTrace()}
}

四、自定义Job

协程构建器基于其父Job构建自己的Job,如果自定义了Job便不再适用父子关系,失去了结构化并发(父协程不会等待子协程完成)。

fun main(): Unit = runBlocking {val scope = CoroutineScope(Job())
//    test1(scope)  //打印:测试1---子协程1test2(scope)    //打印:测试2---子协程1 、测试2---子协程2delay(1000)
}fun test1(scope: CoroutineScope) {scope.launch {launch {println("测试1---子协程1")scope.cancel()}launch {println("测试1---子协程2")}}
}fun test2(scope: CoroutineScope) {scope.launch {launch(Job()) { //此处添加了一个job参数println("测试2---子协程1")scope.cancel()}launch {println("测试2---子协程2")}}
}

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

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

相关文章

引入Bootstrap的CSS样式后,<h>标签、<p>标签等HTML自带的标签被覆写没有?答:覆写了。

引入Bootstrap的CSS样式后,标签、 标签等HTML自带的标签被覆写没有&#xff1f;答&#xff1a;覆写了。 为什么这么说&#xff1f;证据呢&#xff1f; 写一个实例&#xff0c;然后调试模式看一下不就得了。 先看没有引入引入Bootstrap的CSS样式情况。 代码如下&#xff1a; …

一些芯片设计的冷知识

关于芯片物理版图 芯片物理版图是一种用来描述集成电路内部结构和连接的图形文件&#xff0c;它是芯片设计的最终结果&#xff0c;也是芯片制造的依据。芯片物理版图中包含了各种工艺层的信息&#xff0c;例如多晶硅层、金属层、活性区层、接触层等&#xff0c;每一层都有不同…

什么是JavaScript中的严格模式(strict mode)?应用场景是什么?

聚沙成塔每天进步一点点 ⭐ 专栏简介⭐ 严格模式&#xff08;Strict Mode&#xff09;&#xff1a;⭐ 使用场景⭐ 写在最后 ⭐ 专栏简介 前端入门之旅&#xff1a;探索Web开发的奇妙世界 记得点击上方或者右侧链接订阅本专栏哦 几何带你启航前端之旅 欢迎来到前端入门之旅&…

在Git中将本地分支推送到远程仓库

这里很明显 我git云端只有一个master分支 然后 我在本地创建了一个develop分支 然后 现在我想将他放在云端 首先 我们要执行 git checkout -b develop将本地切换到 develop 分支上 因为我这里已经选择的就是了 就不需要了 然后我们执行 git push origin develop这样 刷新云…

DHCP工作过程详解

只有是一个网段的&#xff0c;它才会发送 ARP 请求&#xff0c;获取 MAC 地址。如果发现不是呢&#xff1f;Linux 默认的逻辑是&#xff0c;如果这是一个跨网段的调用&#xff0c;它便不会直接将包发送到网络上&#xff0c;而是企图将包发送到网关。 因为网关要和当前的网络至…

2023 年前端编程 NodeJs 包管理工具 npm 安装和使用详细介绍

npm 基本概述 npm is the world’s largest software registry. Open source developers from every continent use npm to share and borrow packages, and many organizations use npm to manage private development as well. npm 官方网站&#xff1a;https://www.npmjs.…

[EROOR] SpringMVC之500 回调函数报错

首先&#xff0c;检查一下idea里面的报错的原因&#xff0c;我的是jdk的版本的问题。所以更换一下就可以了。

React+Typescript+react-router 6 创建路由操作

本文我们来看看路由的安装 其实路由的操作没有什么变化 但是还是给大家讲一下 那么我们打开项目 在项目终端输入 npm install --save react-router react-router-dom安装 一下 react-router 和 react-router-dom 这都是react开发很基本的插件了 不过大家安装前先注意好我的版…

【MySQL学习笔记】(八)复合查询

在前面的笔记中做的查询基本都是对一张表进行查询&#xff0c;在实际开发中远远不够&#xff0c;本篇文章内容是复合查询相关的笔记。需要用到oracle9i的经典测试表&#xff0c;在笔记&#xff08;六&#xff09;中已经教大家如何导入了。 复合查询 基本查询回顾多表查询子连接…

如何实现24/7客户服务自动化?

传统的客服制胜与否的法宝在于人&#xff0c;互联网时代&#xff0c;对于产品线广的大型企业来说&#xff1a;单靠人力&#xff0c;成本大且效率低&#xff0c;相对于产品相对单一的中小型企业来说&#xff1a;建设传统客服系统的成本难以承受&#xff0c;企业客户服务的转型已…

MSTP + Eth-Trunk配置实验 华为实验手册

1.1 实验介绍 1.1.1 关于本实验 以太网是当今现有局域网LAN&#xff08;Local Area Network&#xff09;采用的最通用的通信协议标准&#xff0c;以太网作为一种原理简单、便于实现同时又价格低廉的局域网技术已经成为业界的主流。 本实验主要介绍了LAN网络中的Eth-Trunk技术…

NameError: name ‘add start docstrings to callable‘ is not defined解决方案

大家好,我是爱编程的喵喵。双985硕士毕业,现担任全栈工程师一职,热衷于将数据思维应用到工作与生活中。从事机器学习以及相关的前后端开发工作。曾在阿里云、科大讯飞、CCF等比赛获得多次Top名次。现为CSDN博客专家、人工智能领域优质创作者。喜欢通过博客创作的方式对所学的…

MYSQL的慢查询

通过查询SQL的执行频次&#xff0c;我们就能够知道当前数据库到底是增删改为主&#xff0c;还是查询为主。 那假如说是以查询为主&#xff0c;次数我们可以借助于慢查询日志。接下来&#xff0c;我们就来介绍一下MySQL中的慢查询日志。 慢查询日志 慢查询日志记录了所有执行时间…

51单片机的简易计算器数码管显示仿真设计( proteus仿真+程序+原理图+报告+讲解视频)

51单片机的简易计算器数码管显示仿真设计 1.主要功能&#xff1a;2.仿真3. 程序代码4. 原理图5. 设计报告6. 设计资料内容清单&&下载链接 51单片机的简易计算器数码管显示仿真设计( proteus仿真程序原理图报告讲解视频&#xff09; 仿真图proteus7.8及以上 程序编译器…

微信小程序AI类目-深度合成-AI问答/AI绘画 互联网信息服务算法备案审核通过教程

近期小程序审核规则变化后&#xff0c;很多使用人类小徐提供的chatGPT系统的会员上传小程序无法通过审核&#xff0c;一直提示需要增加深度合成-AI问答、深度合成-AI绘画类目&#xff0c;该类目需要提供互联网信息服务算法备案并上传资质&#xff0c;一般对企业来说这种务很难实…

[超硬核] 5000字带走读DuckDB优化器之常量折叠与比较简化

DuckDB优化器之常量折叠与比较简化 本篇文章适合学习C的小伙伴&#xff0c;适合阅读开源项目的小伙伴&#xff0c;更适合学习数据库的小伙伴&#xff0c;欢迎与我一起探索优化器知识。 目录 DuckDB优化器之常量折叠与比较简化1.优化器规则2.表达式重写 2.1 重写/访问算子2.2 应…

vue.js+nodejs家庭个人理财收支管理系统5x6nf

本收支管理系统以vue.js作为框架&#xff0c;nodejs语言&#xff0c;B/S模式以及MySql作为后台运行的数据库。本系统主要包括以下功能模块&#xff1a;用户管理、收入分类、支出分类、每日收入、每日支出等模块。 本文的组织结构如下&#xff1a; 1、绪论。综述了本文的研究背景…

RTSP流媒体服务器EasyNVR视频平台以服务方式启动异常却无报错,该如何解决?

EasyNVR是基于RTSP/Onvif协议的安防视频云服务平台&#xff0c;可实现设备接入、实时直播、录像、检索与回放、云存储、视频分发、级联等视频能力服务&#xff0c;可覆盖全终端平台&#xff08;电脑、手机、平板等终端&#xff09;&#xff0c;在智慧工厂、智慧工地、智慧社区、…

Vue + Element UI 实现权限管理系统 前端篇(四):优化登录流程

完善登录流程 1. 丰富登录界面 1.1 从 Element 指南中选择组件模板丰富登录界面&#xff0c;放置一个登录界面表单&#xff0c;包含账号密码输入框和登录重置按钮。 <template><el-form :model"loginForm" :rules"fieldRules" ref"loginFo…

RabbitMq消息模型-队列消息

队列消息分为2种&#xff1a; 基本模型&#xff08;SimpleQueue&#xff09;、工作模型&#xff08;WorkQueue&#xff09; 队列消息特点&#xff1a; 消息不会丢失 并且 有先进先出的顺序。消息接收是有顺序的&#xff0c;不是随机的&#xff0c;仅有一个消费者能拿到数据&…