【kotlin协程】你以为的cancel也许并不cancel

【kotlin协程】你以为的cancel也许并不cancel

先贴上一句cancel名言:

当你调用 cancel() 后,Kotlin 协程的取消并不是立即生效的,特别是在某些情况下,例如当它正在进行的异步操作,而这些操作可能需要一些时间才能完成时。

当初次,甚至没遇到相关问题之前,看到这句话时都是云里雾里的,到底什么叫:协程的取消并不是立即生效的 ?

那么,接下来的几个例子,让你知道:为何我明明已经cancel了,被cancel协程块还在执行呢?

父子协程的关系

你爹永远是你爹,我们需要知道一个概念是,在某个 launch 内部进行 launch 时,当外部 launchcancel时,内部的 launch 也将被取消,代码层面来理解就是:

kotlin 复制代码
 fun main() {
     val scope = CoroutineScope(Dispatchers.Default)
     scope.launch {
         val jobParent = launch {
             launch {
                 repeat(10) {
                     println("child running, isActive: $isActive")
                     delay(1000L)
                 }
                 println("child end")
             }
 ​
             while (true) {
                 println("parent running, isActive: $isActive")
                 delay(1000L)
             }
         }
 ​
         // helper launch
         launch {
             delay(5000L) // 等待父子协程执行5秒后, 取消父协程
             jobParent.cancel()
             println("end, jobParent isActive: ${jobParent.isActive}")
         }
     }
 ​
     // Thread.sleep(5000L) //等待5秒, 避免协程未开启程序就已经结束
     while (true);
 }

输出结果如下(注意,因为协程的调度原因,输出顺序并不完全如下):

text 复制代码
 parent running, isActive: true
 child running, isActive: true
 parent running, isActive: true
 child running, isActive: true
 parent running, isActive: true
 child running, isActive: true
 parent running, isActive: true
 child running, isActive: true
 parent running, isActive: true
 child running, isActive: true
 end, jobParent isActive: false

当5秒后,父协程被cancel了,此时内部的子协程也随着 jobParent 的取消而取消了。

让人误解的 cancel

以上代码逻辑看起来似乎也没什么毛病,该取消的和不该取消的都被取消了。

那么,接下来,进入今天的主题,cancel 为何并不 cancel

将上面的代码稍微修改一下,来达到调用 cancel 之后,协程内部逻辑仍在继续执行的情况。

kotlin 复制代码
 fun main() {
     val scope = CoroutineScope(Dispatchers.Default)
     scope.launch {
         val jobParent = launch {
             launch {
                 repeat(10) {
                     println("child running, isActive: $isActive")
                     // 这里调用线程的睡眠, 来模拟第三方库的耗时请求 (如:间隔1秒钟输出一行文本内容的业务逻辑)
                     runCatching { Thread.sleep(1000) }
                 }
                 println("child end")
             }
 ​
             while (true) {
                 println("parent running, isActive: $isActive")
                 delay(1000L)
             }
         }
 ​
         // helper launch
         launch {
             delay(3000L) // 等待父子协程执行3秒后, 取消父协程
             jobParent.cancel()
             println("end, jobParent isActive: ${jobParent.isActive}")
         }
     }
 ​
     // Thread.sleep(5000L) //等待5秒, 避免协程未开启程序就已经结束
     while (true);
 }

为了避免过渡的等待,这里将 helper launch 的等待改为了3秒,并修改内部子协程的逻辑,模拟第三方库的耗时请求。

按照惯性思维,当 jobParent.cancel() 被调用后,所有的协程都应该被取消,输出的内容应该和前文并无二致才对。

然而事实却并非如此。

以下是修改后输出的内容(注意,因为协程的调度原因,输出顺序并不完全如下):

text 复制代码
 parent running, isActive: true
 child running, isActive: true
 child running, isActive: true
 parent running, isActive: true
 child running, isActive: true
 parent running, isActive: true
 end, jobParent isActive: false
 child running, isActive: false
 child running, isActive: false
 child running, isActive: false
 child running, isActive: false
 child running, isActive: false
 child running, isActive: false
 child running, isActive: false
 child end

没错,协程的确是结束了。但,耗时操作却仍在执行,尽管父协程已经被取消了, isActive 已经不再保持活跃。

但,它还在执行。

原来, 协程的取消并不是立即生效的 这句话是这么理解的呀。

真是小刀喇屁股,开了眼了。

isActive的妙用

正如上面的输出内容所示,我们可以发现当协程结束之后,不论父子,它们的协程活跃状态 isActive 都变为了 false (事实上,父协程已经被正常cancel掉了) 。

那么?

是否就可以在这isActive上下文章了?

肯定的,我们需要让三方库的耗时操作,在协程不再活跃时,也就是 isActive == false 时终止它。

于是,就可以这么书写。

kotlin 复制代码
 fun main() {
     val scope = CoroutineScope(Dispatchers.Default)
     scope.launch {
         val jobParent = launch {
             launch {
                 repeat(10) {
                     if (!isActive) //当协程不再活跃状态时,手动结束
                         return@repeat
 ​
                     println("child running, isActive: $isActive")
                     // 这里调用线程的睡眠, 来模拟第三方库的耗时请求 (如:间隔1秒钟输出一行文本内容的业务逻辑)
                     runCatching { Thread.sleep(1000) }
                 }
                 println("child end")
             }
 ​
             while (true) {
                 println("parent running, isActive: $isActive")
                 delay(1000L)
             }
         }
 ​
         // helper launch
         launch {
             delay(3000L) // 等待父子协程执行3秒后, 取消父协程
             jobParent.cancel()
             println("end, jobParent isActive: ${jobParent.isActive}")
         }
     }
 ​
     // Thread.sleep(5000L) //等待5秒, 避免协程未开启程序就已经结束
     while (true);
 }

输出的内容如下(注意,因为协程的调度原因,输出顺序并不完全如下):

text 复制代码
 child running, isActive: true
 parent running, isActive: true
 child running, isActive: true
 parent running, isActive: true
 child running, isActive: true
 parent running, isActive: true
 end, jobParent isActive: false
 child end

这时,cancel 才算得上是真正意义上的 cancel

相关推荐
小雨cc5566ru5 小时前
uniapp+Android面向网络学习的时间管理工具软件 微信小程序
android·微信小程序·uni-app
bianshaopeng6 小时前
android 原生加载pdf
android·pdf
hhzz6 小时前
Linux Shell编程快速入门以及案例(Linux一键批量启动、停止、重启Jar包Shell脚本)
android·linux·jar
火红的小辣椒7 小时前
XSS基础
android·web安全
勿问东西9 小时前
【Android】设备操作
android
五味香9 小时前
C++学习,信号处理
android·c语言·开发语言·c++·学习·算法·信号处理
图王大胜11 小时前
Android Framework AMS(01)AMS启动及相关初始化1-4
android·framework·ams·systemserver
工程师老罗13 小时前
Android Button “No speakable text present” 问题解决
android
小雨cc5566ru14 小时前
hbuilderx+uniapp+Android健身房管理系统 微信小程序z488g
android·微信小程序·uni-app
小雨cc5566ru15 小时前
微信小程序hbuilderx+uniapp+Android 新农村综合风貌旅游展示平台
android·微信小程序·uni-app