Kotlin之协程(二)取消

本文深入探讨了Kotlin协程的取消机制,通过实例分析了如何处理无法取消的协程,包括检查`isActive`状态和使用`withContext(NonCancellable)`。此外,还介绍了超时取消的实现,利用`withTimeout`避免长时间运行的任务。这些内容有助于理解和优化协程的控制流程。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

简介

介绍
此篇文章主要介绍了kotlin的取消深入的介绍,如果之前没有接触过协程,可以
参考上一篇内容初始中的内容。
参考文档
  1. 谷歌开发者
  2. Kotlin文档
文章目录

Kotlin之协程(一)初识
Kotlin之协程(二)取消

协程之取消深入

1.概述
简答的取消在上篇初识中已经有所介绍,不再赘述,这篇继续来深入了解协程的取消。
2.取消异常
代码如下
    val job = launch(Dispatchers.Default) {
           try {
               var index = 0
               while (index <10) {
                   delay(200)
                   Log.d(Constants.TAG, "当前位置:${index++}")
               }
           }catch (e:Exception){
               Log.d(Constants.TAG,"异常:${e.message}")
           }
        }
        delay(1000)
        Log.d(Constants.TAG, "要取消了")
        job.cancelAndJoin()
        Log.d(Constants.TAG, "取消完成")
日志结果如下
2021-09-16 11:12:20.710 12740-12789/demo.demo.democoroutines D/Coroutines: 当前位置:0
2021-09-16 11:12:20.912 12740-12789/demo.demo.democoroutines D/Coroutines: 当前位置:1
2021-09-16 11:12:21.113 12740-12789/demo.demo.democoroutines D/Coroutines: 当前位置:2
2021-09-16 11:12:21.314 12740-12789/demo.demo.democoroutines D/Coroutines: 当前位置:3
2021-09-16 11:12:21.506 12740-12740/demo.demo.democoroutines D/Coroutines: 要取消了
2021-09-16 11:12:21.508 12740-12789/demo.demo.democoroutines D/Coroutines: 异常:StandaloneCoroutine was cancelled
2021-09-16 11:12:21.509 12740-12740/demo.demo.democoroutines D/Coroutines: 取消完成
结果分析:

1、在delay延时打印时候取消会先抛出JobCancellationException异常,异常信息:StandaloneCoroutine was cancelled。
2、如果只是cancelAndJoin()则日志如下,如果只是cancel()则异常信息会在完成之后,因为join会占用协程。

3.未能取消的协程
代码如下:
val job = launch(Dispatchers.Default) {
           try {
               var index = 0
               val startTime = System.currentTimeMillis()
               var nextPrintTime = startTime
               while (index <10) {
                   if (System.currentTimeMillis() >= nextPrintTime){
                       Log.d(Constants.TAG, "当前位置:${index++}")
                       nextPrintTime += 200L
                   }
               }
           }catch (e:Exception){
               Log.d(Constants.TAG,"异常:${e}")
           }
        }
        delay(1000)
        Log.d(Constants.TAG, "要取消了")
        job.cancelAndJoin()
        Log.d(Constants.TAG, "取消完成")
日志如下:
2021-09-16 11:20:06.746 13747-13798/demo.demo.democoroutines D/Coroutines: 当前位置:0
2021-09-16 11:20:06.946 13747-13798/demo.demo.democoroutines D/Coroutines: 当前位置:1
2021-09-16 11:20:07.146 13747-13798/demo.demo.democoroutines D/Coroutines: 当前位置:2
2021-09-16 11:20:07.346 13747-13798/demo.demo.democoroutines D/Coroutines: 当前位置:3
2021-09-16 11:20:07.546 13747-13798/demo.demo.democoroutines D/Coroutines: 当前位置:4
2021-09-16 11:20:07.745 13747-13747/demo.demo.democoroutines D/Coroutines: 要取消了
2021-09-16 11:20:07.746 13747-13798/demo.demo.democoroutines D/Coroutines: 当前位置:5
2021-09-16 11:20:07.946 13747-13798/demo.demo.democoroutines D/Coroutines: 当前位置:6
2021-09-16 11:20:08.146 13747-13798/demo.demo.democoroutines D/Coroutines: 当前位置:7
2021-09-16 11:20:08.346 13747-13798/demo.demo.democoroutines D/Coroutines: 当前位置:8
2021-09-16 11:20:08.546 13747-13798/demo.demo.democoroutines D/Coroutines: 当前位置:9
2021-09-16 11:20:08.548 13747-13747/demo.demo.democoroutines D/Coroutines: 取消完成

结果分析:
1、由现象可以可出,协程在取消了之后,仍然还在进行任务,如果为while(true)则会一直执行,因为没有delay,cpu一直在进行运算占用,则此时即使调用了calcelAndJoin()任务仍然会继续执行,取消不了。
2、cancelAndJoin()会占用协程,所以直到最后"取消完成"才打印出来,如果是cancel()则会和"要取消了"一起打印出来。

4.解决未能取消的协程问题
思路:

协程在取消之后状态会发生变化,已经不处在活跃状态,所以我们在循环判断的时候注意判断协程的状态,如果占用的任务不是循环形式则可以定时进行判断协程的状态。

上述3中的未能取消的问题解决,代码如下:
val job = launch(Dispatchers.Default) {
           try {
               var index = 0
               val startTime = System.currentTimeMillis()
               var nextPrintTime = startTime
               while (index <10 && isActive) {//此处根据isActive,处在active状态再进行继续
                   if (System.currentTimeMillis() >= nextPrintTime){
                       Log.d(Constants.TAG, "当前位置:${index++}")
                       nextPrintTime += 200L
                   }
               }
           }catch (e:Exception){
               Log.d(Constants.TAG,"异常:${e}")
           }
        }
        delay(1000)
        Log.d(Constants.TAG, "要取消了")
        job.cancelAndJoin()
        Log.d(Constants.TAG, "取消完成")
日志如下:
2021-09-16 11:29:16.938 14216-14313/demo.demo.democoroutines D/Coroutines: 当前位置:0
2021-09-16 11:29:17.136 14216-14313/demo.demo.democoroutines D/Coroutines: 当前位置:1
2021-09-16 11:29:17.336 14216-14313/demo.demo.democoroutines D/Coroutines: 当前位置:2
2021-09-16 11:29:17.536 14216-14313/demo.demo.democoroutines D/Coroutines: 当前位置:3
2021-09-16 11:29:17.736 14216-14313/demo.demo.democoroutines D/Coroutines: 当前位置:4
2021-09-16 11:29:17.936 14216-14313/demo.demo.democoroutines D/Coroutines: 当前位置:5
2021-09-16 11:29:17.945 14216-14216/demo.demo.democoroutines D/Coroutines: 要取消了
2021-09-16 11:29:17.950 14216-14216/demo.demo.democoroutines D/Coroutines: 取消完成
结果分析:

1、可以看出在cancelAndJoin()之后任务已经不再继续执行(即取消成功了),因为此时协程的isActive已经不是true了。
2、第二种解决办法(定时检测状态),可以看出也能达到取消的效果:

代码如下:
 var isAlive = true
        val job = launch(Dispatchers.Default) {
           try {
               var index = 0
               val startTime = System.currentTimeMillis()
               var nextPrintTime = startTime
               while (index <10 && isAlive) {//此处根据isActive,处在active状态再进行继续
                   if (System.currentTimeMillis() >= nextPrintTime){
                       Log.d(Constants.TAG, "当前位置:${index++}")
                       nextPrintTime += 200L
                   }
               }

           }catch (e:Exception){
               Log.d(Constants.TAG,"异常:${e}")
           }
        }
        launch(Dispatchers.IO) {
            while (isAlive){
                delay(200)
                isAlive  = job.isActive
                Log.d(Constants.TAG,"当前协程状态:$isAlive")
            }
        }
        delay(1000)
        Log.d(Constants.TAG, "要取消了")
        job.cancel()
        Log.d(Constants.TAG, "取消完成")
日志如下:
2021-09-16 11:51:46.957 17264-17300/demo.demo.democoroutines D/Coroutines: 当前位置:0
2021-09-16 11:51:47.156 17264-17300/demo.demo.democoroutines D/Coroutines: 当前位置:1
2021-09-16 11:51:47.162 17264-17301/demo.demo.democoroutines D/Coroutines: 当前协程状态:true
2021-09-16 11:51:47.356 17264-17300/demo.demo.democoroutines D/Coroutines: 当前位置:2
2021-09-16 11:51:47.363 17264-17301/demo.demo.democoroutines D/Coroutines: 当前协程状态:true
2021-09-16 11:51:47.556 17264-17300/demo.demo.democoroutines D/Coroutines: 当前位置:3
2021-09-16 11:51:47.564 17264-17302/demo.demo.democoroutines D/Coroutines: 当前协程状态:true
2021-09-16 11:51:47.756 17264-17300/demo.demo.democoroutines D/Coroutines: 当前位置:4
2021-09-16 11:51:47.765 17264-17302/demo.demo.democoroutines D/Coroutines: 当前协程状态:true
2021-09-16 11:51:47.956 17264-17300/demo.demo.democoroutines D/Coroutines: 当前位置:5
2021-09-16 11:51:47.960 17264-17264/demo.demo.democoroutines D/Coroutines: 要取消了
2021-09-16 11:51:47.961 17264-17264/demo.demo.democoroutines D/Coroutines: 取消完成
2021-09-16 11:51:47.966 17264-17302/demo.demo.democoroutines D/Coroutines: 当前协程状态:false
5.人为设置任务不能取消

1.我们先看一个现象

代码如下:
 val job = launch {
            try {
                repeat(100) {
                    delay(200)
                    Log.d(Constants.TAG, "任务执行:$it")
                }
            } catch (e: Exception) {
                Log.d(Constants.TAG, "异常:${e.message}")
            } finally {
                Log.d(Constants.TAG, "finally")
                delay(1000)
                Log.d(Constants.TAG, "finally_delay")
            }
        }
        delay(2000)
        Log.d(Constants.TAG, "要取消了")
        job.cancelAndJoin()
        Log.d(Constants.TAG, "取消完成")
日志如下:
2021-09-16 13:45:12.886 20483-20483/demo.demo.democoroutines D/Coroutines: 任务执行:0
2021-09-16 13:45:13.090 20483-20483/demo.demo.democoroutines D/Coroutines: 任务执行:1
2021-09-16 13:45:13.294 20483-20483/demo.demo.democoroutines D/Coroutines: 任务执行:2
2021-09-16 13:45:13.498 20483-20483/demo.demo.democoroutines D/Coroutines: 任务执行:3
2021-09-16 13:45:13.704 20483-20483/demo.demo.democoroutines D/Coroutines: 任务执行:4
2021-09-16 13:45:13.909 20483-20483/demo.demo.democoroutines D/Coroutines: 任务执行:5
2021-09-16 13:45:14.114 20483-20483/demo.demo.democoroutines D/Coroutines: 任务执行:6
2021-09-16 13:45:14.318 20483-20483/demo.demo.democoroutines D/Coroutines: 任务执行:7
2021-09-16 13:45:14.522 20483-20483/demo.demo.democoroutines D/Coroutines: 任务执行:8
2021-09-16 13:45:14.690 20483-20483/demo.demo.democoroutines D/Coroutines: 要取消了
2021-09-16 13:45:14.702 20483-20483/demo.demo.democoroutines D/Coroutines: 异常:StandaloneCoroutine was cancelled
2021-09-16 13:45:14.702 20483-20483/demo.demo.democoroutines D/Coroutines: finally
2021-09-16 13:45:14.707 20483-20483/demo.demo.democoroutines D/Coroutines: 取消完成
现象分析:

1、我们可以看到,取消了之后排除了异常,但是finally也执行了,但是finally_delay却没有执行,原因是因为在job取消了之后已经不能再挂起了,在delay(1000)的时候抛出了异常,所以finally_delay未能执行。

delay(1000)异常验证并执行finally_delay:

将上述finally代码改造如下:

Log.d(Constants.TAG, "finally")
                try {
                    delay(1000)
                }catch (e:Exception){
                    Log.d(Constants.TAG, "finally_delay异常:${e.message}")
                }finally {
                    Log.d(Constants.TAG, "finally_delay")
                }

部分日志如下:

2021-09-16 13:53:50.347 20753-20753/demo.demo.democoroutines D/Coroutines: 要取消了
2021-09-16 13:53:50.360 20753-20753/demo.demo.democoroutines D/Coroutines: 异常:StandaloneCoroutine was cancelled
2021-09-16 13:53:50.360 20753-20753/demo.demo.democoroutines D/Coroutines: finally
2021-09-16 13:53:50.361 20753-20753/demo.demo.democoroutines D/Coroutines: finally_delay异常:StandaloneCoroutine was cancelled
2021-09-16 13:53:50.361 20753-20753/demo.demo.democoroutines D/Coroutines: finally_delay
2021-09-16 13:53:50.363 20753-20753/demo.demo.democoroutines D/Coroutines: 取消完成

可以看出在finally之后delay(1000)挂起的时候抛出了异常,异常信息:StandaloneCoroutine was cancelled,而在下一个finally中的finally_delay也执行了,但是没有进行挂起延时而已。

在finally中实现挂起延时利器withContext(NonCancellable) {……}

代码如下:

 val job = launch {
            try {
                repeat(100) {
                    delay(200)
                    Log.d(Constants.TAG, "任务执行:$it")
                }
            } catch (e: Exception) {
                Log.d(Constants.TAG, "异常:${e.message}")
            } finally {
                withContext(NonCancellable){
                    Log.d(Constants.TAG, "finally")
                    delay(1000)
                    Log.d(Constants.TAG, "finally_delay")
                }
            }
        }
        delay(2000)
        Log.d(Constants.TAG, "要取消了")
        job.cancelAndJoin()
        Log.d(Constants.TAG, "取消完成")

日志如下:

2021-09-16 13:58:39.077 20983-20983/demo.demo.democoroutines D/Coroutines: 任务执行:0
2021-09-16 13:58:39.284 20983-20983/demo.demo.democoroutines D/Coroutines: 任务执行:1
2021-09-16 13:58:39.487 20983-20983/demo.demo.democoroutines D/Coroutines: 任务执行:2
2021-09-16 13:58:39.693 20983-20983/demo.demo.democoroutines D/Coroutines: 任务执行:3
2021-09-16 13:58:39.897 20983-20983/demo.demo.democoroutines D/Coroutines: 任务执行:4
2021-09-16 13:58:40.101 20983-20983/demo.demo.democoroutines D/Coroutines: 任务执行:5
2021-09-16 13:58:40.305 20983-20983/demo.demo.democoroutines D/Coroutines: 任务执行:6
2021-09-16 13:58:40.509 20983-20983/demo.demo.democoroutines D/Coroutines: 任务执行:7
2021-09-16 13:58:40.713 20983-20983/demo.demo.democoroutines D/Coroutines: 任务执行:8
2021-09-16 13:58:40.876 20983-20983/demo.demo.democoroutines D/Coroutines: 要取消了
2021-09-16 13:58:40.884 20983-20983/demo.demo.democoroutines D/Coroutines: 异常:StandaloneCoroutine was cancelled
2021-09-16 13:58:40.890 20983-20983/demo.demo.democoroutines D/Coroutines: finally
2021-09-16 13:58:41.897 20983-20983/demo.demo.democoroutines D/Coroutines: finally_delay
2021-09-16 13:58:41.901 20983-20983/demo.demo.democoroutines D/Coroutines: 取消完成

可以看出finally_delay实现了延时执行。

6.超时取消
代码如下:
 launch {
            try {
                withTimeout(2000){
                    repeat(100) {
                        delay(200)
                        Log.d(Constants.TAG, "任务执行:$it")
                    }
                }
            } catch (e: Exception) {
                Log.d(Constants.TAG, "异常:${e}")
            }
        }
日志如下:
2021-09-16 14:10:38.187 22160-22160/demo.demo.democoroutines D/Coroutines: 任务执行:0
2021-09-16 14:10:38.388 22160-22160/demo.demo.democoroutines D/Coroutines: 任务执行:1
2021-09-16 14:10:38.589 22160-22160/demo.demo.democoroutines D/Coroutines: 任务执行:2
2021-09-16 14:10:38.789 22160-22160/demo.demo.democoroutines D/Coroutines: 任务执行:3
2021-09-16 14:10:38.990 22160-22160/demo.demo.democoroutines D/Coroutines: 任务执行:4
2021-09-16 14:10:39.190 22160-22160/demo.demo.democoroutines D/Coroutines: 任务执行:5
2021-09-16 14:10:39.391 22160-22160/demo.demo.democoroutines D/Coroutines: 任务执行:6
2021-09-16 14:10:39.593 22160-22160/demo.demo.democoroutines D/Coroutines: 任务执行:7
2021-09-16 14:10:39.798 22160-22160/demo.demo.democoroutines D/Coroutines: 任务执行:8
2021-09-16 14:10:39.990 22160-22160/demo.demo.democoroutines D/Coroutines: 异常:kotlinx.coroutines.TimeoutCancellationException: Timed out waiting for 2000 ms
现象分析:

1、可以看出在2000ms之后超时抛出了异常,并且任务停止了运行。

总结

本文主要介绍了协程各种取消的处理及各种取消的适用情况,对一些情况的解决思路进行分析,
当然这也是本人的学习笔记,希望也能对大家有那么一点点的帮助或者启发,我就很开心了。
当然了本人也是在学习与理解的过程中记录与理解难免有一定的认知局限性,如果发现有什
么问题,欢迎指正,谢谢。
### 如何正确地取消 Kotlin 协程 #### 创建可取消协程Kotlin 中,创建一个可以被取消协程通常涉及使用 `launch` 或者 `async` 函数启动一个新的协程。为了确保能够正常取消协程,在定义协程时应当指定其所属的作用域[^4]。 ```kotlin val scope = CoroutineScope(Job()) scope.launch { // 执行异步任务... } ``` #### 使用 `SupervisorJob` 和 `CoroutineScope` 当多个子协程共享同一个父级作用域时,如果其中一个子协程失败,则整个作用域中的其他所有活动协程都将被取消。为了避免这种情况发生,可以考虑使用 `SupervisorJob` 来代替默认的 `Job()`,这样即使某个子协程抛出了未捕获异常也不会影响到其他的兄弟协程。 ```kotlin val supervisor = SupervisorJob() val parentScope = CoroutineScope(supervisor) // 子协程1 parentScope.launch { delay(1000L) println("Child 1 completed.") } // 子协程2 parentScope.launch { try { delay(Long.MAX_VALUE) // 模拟长时间运行的任务 } catch (e: CancellationException) { println("Child 2 was cancelled.") } } delay(500L) supervisor.cancel() // 取消父作用域下的所有子协程 println("All children have been cancelled.") ``` #### 定期检查取消状态 为了让协程能够在接收到取消请求后及时响应并停止执行当前的工作流,可以在协程内部周期性地调用 `isActive` 属性来进行自我检测;也可以利用诸如 `yield()`、`withTimeoutOrNull()` 这样的内置挂起函数来实现相同的效果[^3]。 ```kotlin coroutineScope { launch { while(isActive){ doSomeWork() yield() // 让出CPU时间片给其他协程,并且会检查是否已经被取消 } } } ``` #### 注册取消回调 对于那些可能持有外部资源(如网络连接或文件句柄)的情况来说,在协程结束之前完成必要的清理工作是非常重要的。为此,可以通过注册 `invokeOnCancellation` 回调方法来确保无论因为什么原因导致协程终止都能触发相应的收尾逻辑[^5]。 ```kotlin suspend fun performTaskWithResource(): Unit = withContext(NonCancellable) { // 防止此部分代码因上级取消而中断 suspendCancellableCoroutine<Unit> { cont -> val resource = acquireResource() cont.invokeOnCancellation { releaseResource(resource) } try { useResource(resource) cont.resume(Unit) } finally { if (!cont.isCompleted) { releaseResource(resource) } } } } ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值