协程中的取消和异常 (取消操作详解)

在开发中,我们要避免不必要的的任务来节约设备的内存和电量的使用,协程也是如此。在使用的过程我们需要控制好它的生命周期,在不需要它的取消它。

调用cancel方法

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

当启动了很多个协程,我们一个个协程的取消比较麻烦,我们可以通过取消整个作用域来解决这个问题,因为取消作用域可以取消该作用域创建的所有协程。

/ 假设我们已经定义了一个作用域

val job1 = scope.launch { … }
val job2 = scope.launch { … }

scope.cancel()

假设我们创建了一个作用域scope,并创建了两个协程job1和job2。我们通过调用scope.cancel(),取消作用域,将会把job1 和job2两个协程都取消。

单独取消某个协程,不会影响他的兄弟协程

我们创建了两个协程,job1和job2.我们单独取消job1,不会影响到job2


// 假设我们已经定义了一个作用域

val job1 = scope.launch { … }
val job2 = scope.launch { … }
 
// 第一个协程将会被取消,而另一个则不受任何影响
job1.cancel()

协程通过抛出一个特殊的异常 CancellationException 来处理取消操作

在调用cancel函数的时候,我们需要传入一个CancellationException对象,如果我们没有传入,那就用默认的defaultCancellationException。

 // external cancel with cause, never invoked implicitly from internal machinery
    public override fun cancel(cause: CancellationException?) {
        cancelInternal(cause ?: defaultCancellationException())
    }

一旦抛出了CancellationException,我们就可以通过这一机制来处理协程的取消。在底层的实现中,子协程会通过抛出异常的方式将取消的情况通知它的父级,父协程通过传入的取消原因决定是否处理该异常。

不能在已取消的作用域中再次启动新的协程

调用了 cancel 方法为什么协程处理的任务没有停止?

不同的Diapatcher不同的区别,下一篇文章将介绍。
我们以Dispatchers.Default为例子

import kotlinx.coroutines.*

suspend fun main() = runBlocking {
    var startTime = System.currentTimeMillis()
    val job = launch(Dispatchers.Default) {
        var nextTime = startTime
        var i = 0
        while (i < 5) {
            if (System.currentTimeMillis() >= nextTime) {
                println("这是第${i}次")
                i++
                //1000毫秒执行一次
                nextTime += 1000
            }
        }
    }
    delay(1000)
    println("取消")
    job.cancel()
    println("取消完毕")

}

这是第0次
这是第1次
取消
取消完毕
这是第2次
这是第3次
这是第4次

调用cancel方法之后,协程的任务依然在运行。调用cancel方法的时候,此时协程处于cancelling正在取消的状态,接着我们打印了2,3,4,处理任务结束之后,协程变成cancelled已经取消的状态,这是以Default举例,Default调度会等待协程任务处理完毕才取消。

让协程可以被取消

协程处理任务都是协作式的,协作的意思就是我们的处理任务要配合协程取消做处理。因此在执行任务期间我们要定时检查协程的状态是否已经取消,例如我们从磁盘读取文件之前我们先检查协程是否被取消了。


val job = launch {
    for(file in files) {
        // TODO 检查协程是否被取消
        readFile(file)
    }
}

协程中的挂起函数都是可取消的,使用他们的时候,我们不需要检查协程是否已取消。例如withContext,delay 。如果没有这些挂起函数,为了让我们的代码配合协程取消,可以使用一下两种方法:

  • 检查 job.isActive 或者使用 ensureActive()
  • 使用 yield() 来让其他任务进行

检查 job 的活跃状态

先看一下第一种方法,在我们的 while(i<5) 循环中添加对于协程状态的检查:

// 因为处于 launch 的代码块中,可以访问到 job.isActive 属性
while (i < 5 && isActive)

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

Job.join 和 Deferred.await cancellation

等待协程处理结果有两种方法,launch启动的job可以调用join,async 返回的Deferred 可以调用await方法

  • job.join会让协程挂起,直到等待协程处理任务完毕,我们可以配合cancel使用
  • deferred.await()如果我们关心协程的处理结果,我们可以使用deferred。结果由deferred.await返回。也是job类型,也可以被取消。

处理协程取消的副作用

当我们需要在协程取消 后处理一些清理的工作,或者做一些打印日志。我们有几种办法:

  • 通过检查协程的状态

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

当判断协程不是isActive状态的时候,我们可以做一些清理

  • try catch finally
    我们知道协程的取消会抛出CancellationException 异常,我们可以在协程提中使用try catch finally,在finally中做我们的一些清理的工作,或者打印日志

val job = launch {
   try {
      work()
   } catch (e: CancellationException){
      println(“Work cancelled!”)
    } finally {
      println(“Clean up!”)
    }
}

delay(1000L)
println(“Cancel!”)
job.cancel()
println(“Done!

已经取消的协程,不能再被挂起

已经取消的协程,不能再被挂起,但是当我们需要在取消的协程中调用挂起函数,那么我们可以在finally中使用NonCancellable ,意思是让协程挂起,直到处理挂起函数中的代码完毕,协程才会取消。


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!

在jetpack中使用viewModelScope 或者lifecycleScope 中定义的作用域,他们在scope完成后取消他们的处理任务。如果我们手动创建自己的作用域CoroutineScope,我们需要协作协程,将我们的作用域和job绑定,在需要的时候取消。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,335评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,895评论 3 387
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,766评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,918评论 1 285
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,042评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,169评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,219评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,976评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,393评论 1 304
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,711评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,876评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,562评论 4 336
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,193评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,903评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,142评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,699评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,764评论 2 351

推荐阅读更多精彩内容