day7 我不要了,这不是肯德基 cancel

Cancellation is important for avoiding doing more work than needed which can waste memory and battery life;

当我们在开发时,很难去追踪每一个coroutine或是一个一个去取消,所以才会透过scope的取消去cancel他所有的子coroutine,而cancel只会影响其child coroutine对他的sibling或parent不会有影响

coroutine的cancel会丢出CancellationException

fun cancel(cause: CancellationException? = null)

实际运行上是由child通知一个Exception, parent检查他是甚麽Exception,如果是CancellationException,parent就不会执行动作,反之则会向上传递(参阅Exception)

当一个coroutine被cancel之後,便无法再往里面加入新的coroutine,然而当我们呼叫cancel()的时候,coroutine并不会立刻关闭,而是会先转移到canceling state等到工作结束後再转换成cancelled state


val scope = CoroutineScope(Job())
val rootJob = scope.launch {
    for (i in (1..10)){
        Thread.sleep(100)
        Timber.d(i.toString())
    }
}
lifecycleScope.launch {
    delay(300)
    Timber.d("isActive ${rootJob.isActive}, isCancelled ${rootJob.isCancelled}")
    rootJob.cancel()
    Timber.d("ask cancel")
    Timber.d("isActive ${rootJob.isActive}, isCancelled ${rootJob.isCancelled}")
}
/**
 * 1
 * 2
 * 3
 * isActive true, isCancelled false
 * ask cancel
 * isActive false, isCancelled true
 * 4
 * 5
 * 6
 * 7
 * 8
 * 9
 * 10
 * */

解释一下,为甚麽要用Thread.sleep而不用delay

简单讲的话,delay会检查Job的状态,如果isCancelled == true,就会发出CancellationException

取消是协作的

我们希望我们的coroutine是可被取消的,所以需要定期检查或是在执行长时间任务前检查,透过job.isActive or ensureActive()或是透过yield关键字

检查Job状态

尽管delay()会自动检查job的状态,但我们没事不会用他呀,还是要了解一下怎麽用job的检查,他其实也很直白,isActive和ensureActive(),你们看了就会懂得

for (i in (1..10)){
    if (this.isActive){

        Thread.sleep(100)
        Timber.d(i.toString())
    }
}

//or
for (i in (1..10)){
    ensureActive()
    Thread.sleep(100)
    Timber.d(i.toString())
}
//or
while(i <= 10 && this.isActive)

yield

这里就yield关键字做介绍,我觉得网路上尽管资源很多,但我通常看完还是一脸蒙
meme

python yield
python yield
kotlin yield

这边给三篇我觉得讲得够简单,又有切到概念的文章

在文中有讲到,yield在其他语言大概是 “return the value,and continue when you next enter。”这个意思,当然学程序总是离不开文档,这边就以文档的例子讲解

// inferred type is Sequence<Int>
val fibonacci = sequence {
    yield(1) // first Fibonacci number
    var cur = 1
    var next = 1
    while (true) {
        yield(next) // next Fibonacci number
        val tmp = cur + next
        cur = next
        next = tmp
    }
}

println(fibonacci.take(10).joinToString())
//1, 1, 2, 3, 5, 8, 13, 21, 34, 55

有懂吗?会让出当前thread,让其他任务执行,如果让出後没有其他任务要在该thread执行,会继续执行
Yields the thread (or thread pool) of the current coroutine dispatcher to other coroutines on the same dispatcher to run if possible.
注意,只有在同个thread的任务可以透过yield切换,如果用main和io去跑,yield并不会有作用

lifecycleScope.launch {
    Timber.d("main1")
    yield()

    Timber.d("main2")
    yield()

    Timber.d("main3")

}

lifecycleScope.launch {
    Timber.d("main -2 1")
    yield()

    Timber.d("main -2 2")
    yield()

    Timber.d("main -2 3")
}
/**
 * main1
 * main -2 1
 * main2
 * main -2 2
 * main3
 * main -2 3
 * */

可是等等,这个特性并不能保证job的状态检查呀
没错,他和delay类似,会自动检查job状态,但我认为在需要上述特性时,再用这个关键字会更好,不然用isActive或ensureActive()即可,也能避免粗心而发生执行顺序乱掉

在取消时执行任务

当job执行到canceling,状态时,尽管还没结束,他却不能再执行suspend,但如果用try/catch/finally的模式,或是有某种特殊需求,必须这麽做(别问我,我想不到

runBlocking<Unit> {
   val job = launch (Dispatchers.Default) {
        try {
        	delay(1000L)
        } finally {
            withContext(NonCancellable){
                delay(1000L)
                println("Cleanup done!")
        	}
        }
    }
    delay(1000L)
    println("Cancel!")
    job.cancel()
    println("Done!")
}

注意,没事不要用,要用时只能用withContext
如果用launch(NonCancellable),parent取消时,child会留着,钻石恒久远,一颗永流传的概念,直到整个application销毁,并且parent不会也不会再child throw Exception时被销毁,简单说,问题很多,到时抓bug抓到哭出来

Doing this is very risky as you lose control of the execution of the coroutine. It’s true that it produces more concise and easier to read code but the problems this can cause in the future are unpredictable.

  1. 无法在测试时取消
  2. 在无限循环中,delay或任何检查job的方式都将无法取消
  3. 以此建构flow,flow将无法从外部取消

Recommendation: use it ONLY for suspending cleanup code.

连结整理

了解yield

python yield
python yield
kotlin yield

文档

官方blog cancel
英文文档看exception和cancel
中文文档看Exception和cancel

官方blog pattern for work shouldn't be cancel


<<:  Day.14 Crash Recovery- InnoDB 架构 -> MYSQL 二阶段提交(2PC) _2

>>:  Day22 - 铁人付外挂实作付款类别(ㄧ)- WooCommerce 金流介绍

虹语岚访仲夏夜-15(打杂的Allen篇)

小七离开便利商店後,店员『太子』走了过来... 「Allen 我觉得你走到那,都有灾难。」 我看了看...

轻松小单元 - 最新修正内容(2021)

资安法与时俱进,但也很少会突然多了新应办事项。在草案期间就会公布新内容并举办巡回说明。实际施行也会给...

【从实作学习ASP.NET Core】Day27 | 前台 | PayPal 订单付款 (2)

接续昨天的付款按钮,今天要把自己的订单内容和付款按钮结合 PayPal 订单内容 范例 这边提供一个...

Alpine Linux Porting (2)

一样开头先上成果~ 完整影片可见: https://twitter.com/Ruinland_Mas...

Day 03:Android 开发工具

前言 在开始写 Android 之前,想先介绍如何提升写 code 的速度, 如果从现在开始习惯,并...