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;每一层都有不同…

MySQL/MariaDB 查询某个 / 多个字段重复数据

创建测试表和数据 # 创建表 create table if not exists t_duplicate (name varchar(255) not null,age int not null );# 插入测试数据 insert into t_duplicate(name, age) values(a, 1); insert into t_duplicate(name, age) values(a, 2);查询单个字段重复 使用 count() …

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

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

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

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

Js中一些数组常用API总结

前言 Js中数组是一个重要的数据结构&#xff0c;它相比于字符串有更多的方法&#xff0c;在一些算法题中我们经常需要将字符串转化为数组&#xff0c;使用数组里面的API进行操作。本篇文章总结了一些数组中常用的API&#xff0c;我们把它们分成两类&#xff0c;一类是会改变原…

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.…

关于DNS的一些认识

目录 什么是DNS&#xff1f; 一台具有单个DNS的机器可以拥有多个地址吗&#xff1f; 一台计算机可以有多个属于不同顶级域的DNS名字吗&#xff1f; 什么是DNS&#xff1f; DNS是域名系统&#xff08;Domain Name System&#xff09;的缩写&#xff0c;它是互联网中用于将域名…

[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开发很基本的插件了 不过大家安装前先注意好我的版…

在 uni-app 中选中奇偶子元素

问题描述&#xff1a; 在 uni-app 中,使用 :nth-child() 选择器选择奇偶子元素不像预期那样生效。 原代码&#xff1a; - :nth-child(2n) 选择偶数个子元素 - :nth-child(2n1) 选择奇数个子元素 /* 奇数子元素 */ .issueData_item:nth-child(2n1) {transfo…

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

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

第11节-PhotoShop基础课程-索套工具

文章目录 前言1.索套工具 选中后按Ctrl 可以移动2.加&#xff0c;减&#xff0c;交叉 shift alt 2.多边形索套工具 手动首尾相连 或者双击空地1.单击绘制直线选区2.双击结束绘制3.加&#xff0c;减&#xff0c;交叉4. delete可以删除节点 3.磁性索套工具1.沿着边缘自动吸附2.可…

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

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

解决Oracle SQL语句性能问题——SQL语句改写(in、not in、exists及not exists)

8. in改为join in为Oracle数据库支持的条件语法,该语法会使得代码看起来思路清晰,逻辑分明。该语法有时也会导致SQL语句产生次优的执行计划,而导致SQL语句的性能问题。因此,为了解决相关SQL语句的性能问题,有时我们需要通过join来改写和消除in,具体改写方法如下所示。 …

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博客专家、人工智能领域优质创作者。喜欢通过博客创作的方式对所学的…

Day57 647. 回文子 516.最长回文子序列 动态规划总结篇

文章目录 647. 回文子串516.最长回文子序列动态规划总结篇 647. 回文子串 https://leetcode.cn/problems/palindromic-substrings 布尔类型的dp[i][j]&#xff1a;表示区间范围[i,j] &#xff08;注意是左闭右闭&#xff09;的子串是否是回文子串&#xff0c;如果是dp[i][j]为…

最长公共子序列(上海交通大学考研机试题)

题目描述 给出两个长度为 n 的整数序列&#xff0c;求它们的最长公共子序列&#xff08;LCS&#xff09;的长度&#xff0c;保证第一个序列中所有元素都不重复。 注意&#xff1a; 第一个序列中的所有元素均不重复。 第二个序列中可能有重复元素。 一个序列中的某些元素可能不…