vs 启动调用的目标发生异常_协程中的取消和异常 | 取消操作详解

d77f05a29e737b8125fd9865602cd9e8.png在日常的开发中,我们都知道应该避免不必要的任务处理来节省设备的内存空间和电量的使用——这一原则在协程中同样适用。您需要控制好协程的生命周期,在不需要使用的时候将它取消,这也是结构化并发所倡导的,继续阅读本文来了解有关协程取消的来龙去脉。⚠️ 为了能够更好地理解本文所讲的内容,建议您首先阅读本系列中的第一篇文章: 协程中的取消和异常 | 核心概念介绍。

调用 cancel 方法

当启动多个协程时,无论是追踪协程状态,还是单独取消各个协程,都是件让人头疼的事情。不过,我们可以通过直接取消协程启动所涉及的整个作用域 (scope) 来解决这个问题,因为这样可以取消所有已创建的子协程。
// 假设我们已经定义了一个作用域val job1 = scope.launch { … }val job2 = scope.launch { … }scope.cancel()

取消作用域会取消它的子协程

有时候,您也许仅仅需要取消其中某一个协程,比如用户输入了某个事件,作为回应要取消某个进行中的任务。如下代码所示,调用 job1.cancel 会确保只会取消跟 job1 相关的特定协程,而不会影响其余兄弟协程继续工作。
// 假设我们已经定义了一个作用域val job1 = scope.launch { … }val job2 = scope.launch { … } // 第一个协程将会被取消,而另一个则不受任何影响job1.cancel()

被取消的子协程并不会影响其余兄弟协程

协程通过抛出一个特殊的异常 CancellationException 来处理取消操作。在调用 .cancel 时您可以传入一个 CancellationException 实例来提供更多关于本次取消的详细信息,该方法的签名如下:
fun cancel(cause: CancellationException? = null)
如果您不构建新的 CancellationException 实例将其作为参数传入的话,会创建一个默认的 CancellationException (请查看完整代码)。
public override fun cancel(cause: CancellationException?) {    cancelInternal(cause ?: defaultCancellationException())}
  • 完整代码https://github.com/Kotlin/kotlinx.coroutines/blob/master/kotlinx-coroutines-core/common/src/JobSupport.kt#L612
一旦抛出了 CancellationException 异常,您便可以使用这一机制来处理协程的取消。有关如何执行此操作的更多信息,请参考下面的处理取消的副作用一节。在底层实现中,子协程会通过抛出异常的方式将取消的情况通知到它的父级。父协程通过传入的取消原因来决定是否来处理该异常。如果子协程因为 CancellationException 而被取消,对于它的父级来说是不需要进行其余额外操作的。不能在已取消的作用域中再次启动新的协程如果您使用的是 androidx KTX 库的话,在大部分情况下都不需要创建自己的作用域,所以也就不需要负责取消它们。如果您是在 ViewModel 的作用域中进行操作,请使用 viewModelScope,或者如果在生命周期相关的作用域中启动协程,那就应该使用 lifecycleScope。viewModelScope 和 lifecycleScope 都是 CoroutineScope 对象,它们都会在适当的时间点被取消。例如,当 ViewModel 被清除时,在其作用域内启动的协程也会被一起取消。
  • viewModelScope

    https://developer.android.google.cn/reference/kotlin/androidx/lifecycle/package-summary#(androidx.lifecycle.ViewModel).viewModelScope:kotlinx.coroutines.CoroutineScope

  • lifecycleScope

    https://developer.android.google.cn/reference/kotlin/androidx/lifecycle/package-summary#lifecyclescope

为什么协程处理的任务没有停止?

如果我们仅是调用了 cancel 方法,并不意味着协程所处理的任务也会停止。如果您使用协程处理了一些相对较为繁重的工作,比如读取多个文件,那么您的代码不会自动就停止此任务的进行。

让我们举一个更简单的例子看看会发生什么。假设我们需要使用协程来每秒打印两次 "Hello"。我们先让协程运行一秒,然后将其取消。其中一个版本实现如下所示:

30263f9337e97f3d4032277911a2d33c.gif

我们一步一步来看发生了什么。当调用 launch 方法时,我们创建了一个活跃 (active) 状态的协程。紧接着我们让协程运行了 1,000 毫秒,打印出来的结果如下:
Hello 0Hello 1Hello 2
当 job.cancel 方法被调用后,我们的协程转变为取消中 (cancelling) 的状态。但是紧接着我们发现 Hello 3 和 Hello 4 打印到了命令行中。当协程处理的任务结束后,协程又转变为了已取消 (cancelled) 状态。协程所处理的任务不会仅仅在调用 cancel 方法时就停止,相反,我们需要修改代码来定期检查协程是否处于活跃状态。

让您的协程可以被取消

您需要确保所有使用协程处理任务的代码实现都是协作式的,也就是说它们都配合协程取消做了处理,因此您可以在任务处理期间定期检查协程是否已被取消,或者在处理耗时任务之前就检查当前协程是否已取消。例如,如果您从磁盘中获取了多个文件,在开始读取文件内容之前,先检查协程是否被取消了。类似这样的处理方式,您可以避免处理不必要的 CPU 密集型任务。

val job = launch {    for(file in files) {        // TODO 检查协程是否被取消        readFile(file)    }}
所有 kotlinx.coroutines 中的挂起函数 (withContext, delay 等) 都是可取消的。如果您使用它们中的任一个函数,都不需要检查协程是否已取消,然后停止任务执行,或是抛出 CancellationException 异常。但是,如果没有使用这些函数,为了让您的代码能够配合协程取消,可以使用以下两种方法:
  • 检查 job.isActive 或者使用 ensureActive()
  • 使用 yield() 来让其他任务进行

检查 job 的活跃状态

先看一下第一种方法,在我们的 while(i<5) 循环中添加对于协程状态的检查:
// 因为处于 launch 的代码块中,可以访问到 job.isActive 属性while (i < 5 && isActive)
这样意味着我们的任务只会在协程处于活跃的状态下执行。同样,这也意味着在 while 循环之外,我们若还想处理别的行为,比如在 job 被取消后打日志出来,那就可以检查 !isActive 然后再继续进行相应的处理。

Coroutine 的代码库中还提供了另一个很有用的方法 —— ensureActive(),它的实现如下:

fun Job.ensureActive(): Unit {    if (!isActive) {         throw getCancellationException()    }}
 

如果 job 处于非活跃状态,这个方法会立即抛出异常,我们可以在 while 循环开始就使用这个方法。

while (i < 5) {    ensureActive()    …}

通过使用 ensureActive 方法,您可以避免使用 if 语句来检查 isActive 状态,这样可以减少样板代码的使用量,但是相应地也失去了处理类似于日志打印这种行为的灵活性。

使用 yield() 函数运行其他任务

如果要处理的任务属于 1) CPU 密集型,2) 可能会耗尽线程池资源,3) 需要在不向线程池中添加更多线程的前提下允许线程处理其他任务,那么请使用 yield()。如果 job 已经完成,由 yield 所处理的首要任务将会是检查任务的完成状态,完成的话则直接通过抛出 CancellationException 来退出协程。yield 可以作为定期检查所调用的第一个函数,例如上面提到的 ensureActive() 方法。
  • yield()

    https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/yield.html

Job.join ? Deferred.await cancellation

等待协程处理结果有两种方法: 来自 launch 的 job 可以调用 join 方法,由 async 返回的 Deferred (其中一种 job 类型) 可以调用 await 方法。Job.join 会挂起协程,直到任务处理完成。与 job.cancel 一起使用时,会按照以下方式进行:
  • 如果您调用  job.cancel 之后再调用 job.join,那么协程会在任务处理完成之前一直处于挂起状态;
  • 在 job.join 之后调用 job.cancel 没有什么影响,因为 job 已经完成了。

  • Job.join

    https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-job/join.html

如果您关心协程处理结果,那么应该使用 Deferred。当协程完成后,结果会由 Deferred.await 返回。Deferred 是 Job 的其中一种类型,它同样可以被取消。
  • Deferred

    https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-deferred/index.html

  • Deferred.await

    https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-deferred/await.html

在已取消的 deferred 上调用 await 会抛出 JobCancellationException 异常。

val deferred = async { … }deferred.cancel()val result = deferred.await() // 抛出 JobCancellationException 异常

为什么会拿到这个异常呢?await 的角色是负责在协程处理结果出来之前一直将协程挂起,因为如果协程被取消了那么协程就不会继续进行计算,也就不会有结果产生。因此,在协程取消后调用 await 会抛出 JobCancellationException 异常: 因为 Job 已被取消。

另一方面,如果您在 deferred.cancel 之后调用 deferred.await 不会有任何情况发生,因为协程已经处理结束。

处理协程取消的副作用

假设您要在协程取消后执行某个特定的操作,比如关闭可能正在使用的资源,或者是针对取消需要进行日志打印,又或者是执行其余的一些清理代码。我们有好几种方法可以做到这一点:

检查 !isActive

如果您定期地进行 isActive 的检查,那么一旦您跳出 while 循环,就可以进行资源的清理。之前的代码可以更新至如下版本:

while (i 5 && isActive) {    if (…) {        println(“Hello ${i++}”)        nextPrintTime += 500L    }} // 协程所处理的任务已经完成,因此我们可以做一些清理工作println(“Clean up!”)
您可以查看完整版本。
  • 完整版本

    https://pl.kotl.in/loI9DaIYj

所以现在,当协程不再处于活跃状态,会退出 while 循环,就可以处理一些清理工作了。

Try catch finally

因为当协程被取消后会抛出 CancellationException 异常,我们可以将挂起的任务放置于 try/catch 代码块中,然后在 finally 代码块中执行需要做的清理任务。
val job = launch {   try {      work()   } catch (e: CancellationException){      println(“Work cancelled!”)    } finally {      println(“Clean up!”)    }}delay(1000L)println(“Cancel!”)job.cancel()println(“Done!”)
但是,一旦我们需要执行的清理工作也挂起了,那上述代码就不能够继续工作了,因为一旦协程处于取消中状态,它将不能再转为挂起 (suspend) 状态。您可以查看完整代码。
  • 完整代码https://pl.kotl.in/wjPINnWfG
处于取消中状态的协程不能够挂起

当协程被取消后需要调用挂起函数,我们需要将清理任务的代码放置于 NonCancellable CoroutineContext 中。这样会挂起运行中的代码,并保持协程的取消中状态直到任务处理完成。

val job = launch {   try {      work()   } catch (e: CancellationException){      println(“Work cancelled!”)    } finally {      withContext(NonCancellable){         delay(1000L) // 或一些其他的挂起函数         println(“Cleanup done!”)      }    }}delay(1000L)println(“Cancel!”)job.cancel()println(“Done!”)
您可以查看其工作原理。
  • 工作原理https://pl.kotl.in/ufZRQSa7o

suspendCancellableCoroutine 和 invokeOnCancellation

如果您通过 suspendCoroutine 方法将回调转为协程,那么您更应该使用 suspendCancellableCoroutine 方法。可以使用 continuation.invokeOnCancellation 来执行取消操作:

suspend fun work() {   return suspendCancellableCoroutine { continuation ->       continuation.invokeOnCancellation {           // 处理清理工作       }   // 剩余的实现代码}

为了享受到结构化并发带来的好处,并确保我们并没有进行多余的操作,那么需要保证代码是可被取消的。

使用在 Jetpack: viewModelScope 或者 lifecycleScope 中定义的 CoroutineScopes,它们在 scope 完成后就会取消它们处理的任务。如果要创建自己的 CoroutineScope,请确保将其与 job 绑定并在需要时调用 cancel。

协程代码的取消需要是协作式的,因此请将代码更新为对协程的取消操作以延后的方式进行检查,并避免不必要的操作。

现在,大家了解了本系列的第一部分协程的一些基本概念、第二部分协程的取消,在接下来的文章中,我们将继续深入探讨学习第三部分异常处理,感兴趣的读者请继续关注我们的更新。

6f8770c36ad372673a5bb3ca9c8a2cc5.png


推荐阅读

0e09c956922106dfcbd0e6ee3e67cdd5.png8ccbf5a41a9e2abcb1a5410385104782.pngb83135783517d761310de3687858ba55.png3e96c6e5f134bee3f592a067566989d2.gif点击屏末  | 查看 Android 官方中文文档 —— 使用 Kotlin 更快地编写更出色的 Android 应用

b964e1f09acdf0fc035e5383f3e9cc76.png

ccbd0e1a9fd4e8e14010392962d47729.gif

1f5cdcc544a7dfbabd3f9f1841b3b20b.png

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

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

相关文章

pytorch 图像分割的交并比_Segmentation101系列-最简单的卷积网络语义分割(1)-PASCAL VOC图像分割...

作者&#xff1a;陈洪瀚 /洪瀚笔记知乎专栏摘要&#xff1a;介绍了使用PyTorch和torchvision加载训练好的全卷积网络FCN或DeepLab模型&#xff0c;并对PASCAL VOC图像进行分割并显示结果。网址&#xff1a;github代码链接, 码云代码链接陈洪瀚​www.zhihu.com一. 准备实验数据下…

系统目录结构 ls命令 文件类型 alias命令

2019独角兽企业重金招聘Python工程师标准>>> 2.1/2.2 系统目录结构 /bin&#xff1a;bin是Binary的缩写&#xff0c;该目录下存放的是最常用的命令。 /boot&#xff1a;该目录下存放的是启动Linux时使用的一些核心文件&#xff0c;包括一些连接文件以及镜像文件。 …

运维老鸟教你安装centos6.5如何选择安装包

原文&#xff1a;http://oldboy.blog.51cto.com/2561410/1564620 ------------------------------------------------------------------------------ 近来发现越来越多的运维小伙伴们都有最小化安装系统的洁癖,因此&#xff0c;找老男孩来咨询&#xff0c;这个“洁癖”好习惯…

服务器centos怎么部署_我什么都不会,怎么拥有自己的个人博客呢

博客每个人都想拥有一个属于自己的博客&#xff0c;可以分享自己的心得、技术等&#xff0c;可以很好地展示自己的作品&#xff0c;但是自己又什么都不会怎么才能拥有自己的个人博客呢&#xff1f;一、搭建个人博客需要什么呢(1)购买服务器&#xff0c;个人博客可以购买香港服务…

修改yum的镜像服务器为阿里云

1、进入阿里云镜像网站 http://mirrors.aliyun.com/ 2、选择centos---help 3、安装help里的步骤进行操作 1、备份 mv /etc/yum.repos.d/CentOS-Base.repo /etc/yum.repos.d/CentOS-Base.repo.backup 2、下载新的CentOS-Base.repo 到/etc/yum.repos.d/ CentOS 5 wget -O /e…

java instanceof 继承_Java中的instanceof关键字

Java中&#xff0c;instanceof运算符的前一个操作符是一个引用变量&#xff0c;后一个操作数通常是一个类(可以是接口)&#xff0c;用于判断前面的对象是否是后面的类&#xff0c;或者其子类、实现类的实例。如果是返回true&#xff0c;否则返回false。也就是说&#xff1a;使用…

学习笔记-JMeter 进行接口压力测试

一、压力测试场景设置 1、场景设定&#xff1a;进行接口压力测试时&#xff0c;有单场景也有混合场景。单场景就是对一个接口进行请求&#xff1b;混合场景需要对多个接口进行请求&#xff0c;在流程类业务场景会运用到 2、压测时间设定&#xff1a;通常时间为10&#xff0d;15…

四宫格效果 css_【深度教研】智力游戏“九宫格” 集体教研活动纪实

【关键词】教研要建立过程模式&#xff0c;规范管理&#xff0c;分层推进&#xff0c;各负其责&#xff0c;及时反馈&#xff0c;展示总结。让教研的过程成为全体教师共同成长的过程。游戏和材料不是一次性的制作和一次性的使用&#xff0c;其价值在于反复玩&#xff0c;玩中学…

sql server numeric 可存几位小数_CBA中的10大传奇队长,将篮球精神一直传递下去,你认识几位呢?...

阅读本文前&#xff0c;请您先点击上面的蓝色字体“十点听情感”&#xff0c;再点击“关注”&#xff0c;这样您就可以继续免费收到最新文章了。每天都有分享。完全是免费订阅&#xff0c;请放心关注&#xff01;&#xff01;&#xff01;中国男子篮球职业联赛简称CBA中国篮球自…

设置ntpdate服务开机启动校验时间

一般linux都预装了 ntpdate 服务。 如果没有安装&#xff0c;参考博文&#xff1a;http://blog.csdn.net/zengmingen/article/details/52913486启动设置 1、检查系统服务里有没有ntpdate 使用命令&#xff1a;chkconfig --list2、设置开机启动 chkconfig 的命令详见&#xff1a…

电脑卡顿不流畅是什么原因_什么造成游戏直播画画卡顿、延迟?这三个原因了解一下...

原标题&#xff1a;什么造成游戏直播画画卡顿、延迟&#xff1f;这三个原因了解一下从事直播的朋友们相信都有所体会&#xff0c;直播时很容易出现画面卡顿、延迟的现象&#xff0c;这究竟是什么原因造成的呢&#xff1f;最可能是这几点&#xff01;1、服务器过载崩溃说起服务器…

java linux 调用32位so_从linux源码看socket(tcp)的timeout

从linux源码看socket(tcp)的timeout前言网络编程中超时时间是一个重要但又容易被忽略的问题,对其的设置需要仔细斟酌。在经历了数次物理机宕机之后,笔者详细的考察了在网络编程(tcp)中的各种超时设置&#xff0c;于是就有了本篇博文。本文大部分讨论的是socket设置为block的情况…

spark DAGScheduler、TaskSchedule、Executor执行task源码分析

摘要 spark的调度一直是我想搞清楚的东西&#xff0c;以及有向无环图的生成过程、task的调度、rdd的延迟执行是怎么发生的和如何完成的&#xff0c;还要就是RDD的compute都是在executor的哪个阶段调用和执行我们定义的函数的。这些都非常的基础和困难。花一段时间终于弄白了其中…

代码实现tan graph model for classification_自定义 Estimator 实现(以BERT为例)

本文将主要介绍tensorflow 的Estimator 这个高级API&#xff0c;它的主要作用就是提出一个高级范式&#xff08;paradigm&#xff09;&#xff0c;将模型的训练&#xff0c;验证&#xff0c;预测&#xff0c;以及保存规范起来&#xff0c;免去了tensorflow的Session.run 的操作…

英雄联盟怎么解除小窗口_英雄联盟手游怎么加好友_英雄联盟手游怎么加好友一起玩_资讯...

英雄联盟手游是腾讯联合英雄联盟开发商拳头开发的英雄联盟手游。不仅能够高度还原端游的经典操作和竞技体验&#xff0c;也具有非常多创新的元素&#xff0c;对于英雄联盟的全球生态布局具有重要意义。英雄联盟手游游戏中有非常多的英雄可以供玩家选择&#xff0c;并且拥有排位…

Sonar与jenkins集成

2019独角兽企业重金招聘Python工程师标准>>> 参考文档&#xff1a;http://blog.csdn.net/kefengwang/article/details/54377055 一.下载&#xff1a;wget https://fossies.org/linux/misc/sonarqube-7.0.zip 二.配置sonar.properties ## sudo vim /opt/sonarqube-6.…

eplan连接定义点不显示_EPLAN电气图实例--控制柜(控制面板)

EPLAN电气图实例--控制柜(控制面板)上期回顾(上期主要画了硬件的布局图)&#xff1a;这期主要画一个控制面板控制柜布局1.0 上期主要做了一个长方形的结构板&#xff0c;里面插入了一个结构盒&#xff0c;然后放置一个HMI的宏(这里是KTP1000&#xff0c;在官网随便找下就行了)&…

markdown 语法_markdown特殊语法之上下标

markdown特殊语法之上下标​markdown的基本语法很简单&#xff0c;百度一下就可以了&#xff0c;有空的话我再转载一些过来。我想的是平常其实需要用到的一些输入技巧&#xff0c;特殊用法或者扩展语法&#xff0c;还有一些难点倒是要记录学习一下。在写作的时候&#xff0c;大…

oracle安装向导卡住了_JDK 8 的安装与配置

一、安装环节1. 打开网页https://www.oracle.com/java/technologies/javase/javase-jdk8-downloads.html2.找到对象的版本 尽量从官网下载(官网可能会比较慢&#xff0c;也可以通过浏览器输入jdk版本号进行下载)官网下载需要注册一个账号3.双击下载的 exe,如 jdk-8u131-windows…

web 前端 如何分享到instagram_如何找到靠谱的Web培训机构?web前端培训机构哪个好?...

现如今Web前端开发应用越来越广泛&#xff0c;Web前端工程师人才需求逐年递增&#xff0c;薪资待遇也是水涨船高&#xff0c;也因此吸引了越来越多的人想要迈入Web前端行业&#xff0c;参加Web前端培训是很多人选择学习前端开发技能的途径&#xff0c;那么Web前端培训机构哪个好…