Kotlin之协程(二)取消

简介

介绍

此篇文章主要介绍了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,android,微信)