Kotlin学习笔记24 协程part4 协程的取消与超时

参考链接

示例来自bilibili Kotlin语言深入解析 张龙老师的视频

1 如何取消协程

import kotlinx.coroutines.*

/**
 * 协程的取消
 */

fun main() = runBlocking {
    // 得到启用的协程myJob
    val myJob = GlobalScope.launch {
        repeat(200) { i ->
            println("hello $i")
            delay(500)
        }
    }

    delay(1100)
    println("hello world")
    /**
     * Cancels this job with an optional cancellation cause.
     * A cause can be used to specify an error message or to provide other details on the cancellation reason for debugging purposes.
     * See Job documentation for full explanation of cancellation machinery.
     */
    myJob.cancel(CancellationException("test exception"))// 取消协程 取消协程是一个正常的操作 因此实际上CancellationException不会被打印
    /**
     * Suspends the coroutine until this job is complete.
     * 取消协程不是立马结束的 取消协程时 cancel和join经常成对出现
     */
    myJob.join()
//    myJob.cancelAndJoin() // 上面两句等价于cancelAndJoin
    println("end")
}

class HelloKotlin {
}

2 协程取消的注意点

如果协程正处于某个计算过程中且没有检查取消状态,那么它就是无法被取消的

import kotlinx.coroutines.*

/**
 * 协程取消的注意点
 * 如果协程正处于某个计算过程中且没有检查取消状态,那么它就是无法被取消的
 *
 * kotlinx.coroutines包下所有的挂起函数都可以被取消
 * 当调用取消方法时 他们会检查取消状态,当取消时抛出CancellationException异常
 * 不过 如果协程正处于某个计算过程中且没有检查取消状态,那么它就是无法被取消的(可以理解为取消失败)
 */

fun main() = runBlocking {
    val startTime = System.currentTimeMillis()
    val job = launch(Dispatchers.Default) {// 取得启动的协程job
        var nextTime = startTime
        var i = 0;
        while (i < 20) {// 在时间没有到达指定时间 该while循环一直在空轮转(while循环满足 而 if条件不满足)
            if (System.currentTimeMillis() > nextTime) {// 每500毫秒 i++ 直到i>=20结束
                println("job:I am sleeping ${i++}")
                nextTime += 500
            }
        }
        println("end while")
    }
    delay(1300)
    println("hello world")

    job.cancelAndJoin() // 取消协程 但是协程处于计算中 且协程没有检查取消状态 因此协程没有被取消 一直等待 直到协程执行完毕
    println("welcome")
}

/**
输出如下
job:I am sleeping 0
job:I am sleeping 1
job:I am sleeping 2
hello world
welcome
job:I am sleeping 3
job:I am sleeping 4
job:I am sleeping 5
job:I am sleeping 6
job:I am sleeping 7
job:I am sleeping 8
job:I am sleeping 9
job:I am sleeping 10
job:I am sleeping 11
job:I am sleeping 12
job:I am sleeping 13
job:I am sleeping 14
job:I am sleeping 15
job:I am sleeping 16
job:I am sleeping 17
job:I am sleeping 18
job:I am sleeping 19
end while
 */

class HelloKotlin2 {
}

3 协程取消的注意点

如果协程正处于某个计算过程中 如何正确取消协程

import kotlinx.coroutines.*

/**
 * 协程取消的注意点
 * 如果协程正处于某个计算过程中 如何正确取消协程
 *
 * 有两种方式可以让协程能够被成功取消
 * 1 周期性调用一个挂起函数 该挂起函数会检查取消状态 例如挂起函数delay
 * 2 显示地检查取消状态
 *
 * 下面地示例采用第二种方式取消协程
 */

fun main() = runBlocking {
    val startTime = System.currentTimeMillis()
    val job = launch(Dispatchers.Default) {// 取得启动的协程job
        var nextTime = startTime
        var i = 0;
        /**
         *   Returns true when the current Job is still active (has not completed and was not cancelled yet).
         *   Check this property in long-running computation loops to support cancellation:
         *   while (isActive) {
         *   // do some computation
         *   }
         * isActive是CoroutineScope地一个扩展属性
         * 它当协程仍然处于活跃状态(还没有完成且没有取消)返回true
         * 在长期计算循环中检查该属性可以让协程能够被取消
         */
        while (isActive) {// 与HelloKotlin2唯一不同地方
            if (System.currentTimeMillis() > nextTime) {// 每500毫秒 i++ 直到i>=20结束
                println("job:I am sleeping ${i++}")
                nextTime += 500
            }
        }
        println("end while")
    }
    delay(1300)
    println("hello world")

    job.cancelAndJoin() // 取消协程 但是协程处于计算中 但是它检查取消状态 因此协程立即被取消
    println("welcome")
}

/**
输出:
job:I am sleeping 0
job:I am sleeping 1
job:I am sleeping 2
hello world
end while
welcome
 */

class HelloKotlin3 {
}

4 协程取消的注意点

如果协程正处于某个计算过程中 如何正确取消协程2

import kotlinx.coroutines.*

/**
 * 协程取消的注意点
 * 如果协程正处于某个计算过程中 如何正确取消协程2
 *
 * 有两种方式可以让协程能够被成功取消
 * 1 周期性调用一个挂起函数 该挂起函数会检查取消状态 例如挂起函数delay
 * 2 显示地检查取消状态
 *
 * 下面地示例采用第二种方式取消协程
 * 这里我们实际上用的是HelloKotlin1的代码
 *
 * 对比HelloKotlin3 HelloKotlin3_1之所以可以成功取消协程 因为
 * 1.cancel中修改状态
 * 追踪cancel的实现
 * 以JobSupport为例 按照顺序追踪 cancel cancelInternal cancelImpl makeCancelling 内部最终会修改协程isActive的状态
 * 2.delay这个挂起函数检查state
 * delay:
 * Delays coroutine for a given time without blocking a thread and resumes it after a specified time.
 * This suspending function is cancellable.
 * If the Job of the current coroutine is cancelled or completed while this suspending function is waiting,
 * this function immediately resumes with CancellationException.
 * Note that delay can be used in select invocation with onTimeout clause.
 * Implementation note: how exactly time is tracked is an implementation detail of CoroutineDispatcher in the context.
 * Params:
 * timeMillis - time in milliseconds.
 * 延迟指定时间的协程而不会阻塞线程 并且在等待时间到达的时候 协程能够再次启动
 * 该挂起方法是可以取消的
 * 如果当前协程的job在挂起方法正在等待时被取消或完成
 * 那么该方法会被立马继续并抛出CancellationException
 *
 * 总结一下cancel将isActive更改为false 并且挂起函数可以检测到状态变化/或者可以主动判断isActive的值
 * 说到底 协程要能取消成功 需要感知到isActive状态的变化
 */

fun main() = runBlocking {
    // 得到启用的协程myJob
    val myJob = GlobalScope.launch {
        repeat(200) { i ->
            println("hello $i")
            delay(500)
        }
    }

    delay(1100)
    println("hello world")
    /**
     * Cancels this job with an optional cancellation cause.
     * A cause can be used to specify an error message or to provide other details on the cancellation reason for debugging purposes.
     * See Job documentation for full explanation of cancellation machinery.
     */
    myJob.cancel(CancellationException("test exception"))// 取消协程 取消协程是一个正常的操作 因此实际上CancellationException不会被打印
    /**
     * Suspends the coroutine until this job is complete.
     * 取消协程不是立马结束的 取消协程时 cancel和join经常成对出现
     */
    myJob.join()
//    myJob.cancelAndJoin() // 上面两句等价于cancelAndJoin
    println("end")
}

class HelloKotlin3_1 {
}

5 协程与finally执行顺序

/**
 * 使用finally来关闭资源
 * join 与 cancelAndJoin都会等待所有的清理动作完成之后才会继续往下执行
 */

fun main() = runBlocking {
    val myJob = launch {
        try {
            repeat(100) { i ->
                println("job sleeping $i")
                delay(500)
            }
        } finally {
            println("finally执行") // cancelAndJoin会等待finally执行完毕
        }
    }

    delay(1300)
    println("hello world")
    myJob.cancelAndJoin()
    println("end")
}

class HelloKotlin4 {
}

6 如果已经取消的协程 再调用挂起方法(如delay)不能成功调用

/**
 * 如果已经取消的协程 再调用挂起方法(如delay)不能成功调用
 *
 * 对于下面的示例 当我们在协程的finally中调用挂起函数时 会导致出现CancellationException异常 原因在于运行着该
 * 代码块的协程已经被取消了。通常情况下,这没有什么问题,因为大多数关闭动作(如取消一个job 关闭连接)都是瞬时的(非阻塞的)
 * 并不需要使用挂起函数;然而,在极少数情况下,我们想要在取消的协程中进行挂起操作时,我们可以将代码放置到withContext(NonCancellable){}
 * 代码块中,在这种结构中 我们实际使用了withContext函数与NoCancellable的上下文 我们在下一节演示withContext
 *
 * 以下的示例中在主线程中延时1300毫秒
 * 同时协程在不断的等待输出
 * 当主线程延时结束 取消协程 finally中的delay执行失败 之后的语句不再执行
 */
fun main() = runBlocking {
    val myJob = launch {
        try {
            repeat(100) { i ->
                println("job: I am sleeping $i")
                delay(500)
            }
        } finally {
            println("execute finally")
            delay(1000)// 因为走到这里时协程已经取消 会抛出CancellationException异常 因此后面的代码都不执行了
            println("finally code after delay")
        }
    }

    delay(1300)
    println("hello world")
    myJob.cancelAndJoin()
    println("end")
}

class HelloKotlin5 {
}

7 已经取消的协程 再调用挂起方法(如delay)需要使用withContext代码块

/**
 * 已经取消的协程 再调用挂起方法(如delay)需要使用withContext代码块
 *
 * 当我们在协程的finally中调用挂起函数时 会导致出现CancellationException异常 原因在于运行着该
 * 代码块的协程已经被取消了。我们想要在取消的协程中进行挂起操作时,我们可以将代码放置到withContext(NonCancellable){}
 * 代码块中,在这种结构中 我们实际使用了withContext函数与NoCancellable的上下文
 */
fun main() = runBlocking {
    val myJob = launch {
        try {
            repeat(100) { i ->
                println("job: I am sleeping $i")
                delay(500)
            }
        } finally {
            //
            /**
             * withContext: Calls the specified suspending block with a given coroutine context, suspends until it completes, and returns the result
             *
             * NonCancellable
             * A non-cancelable job that is always active. It is designed for withContext function to prevent cancellation of code blocks that need to be executed without cancellation.
             * 防止代码块中的代码被取消
             * Use it like this:
             * withContext(NonCancellable) {
             * // this code will not be cancelled
             * }
             */
            withContext(NonCancellable) {// 注意与上一个demo对比差异
                println("execute finally")
                delay(1000)
                println("finally code after delay")
            }
        }
    }

    delay(1300)
    println("hello world")
    myJob.cancelAndJoin()
    println("end")
}

class HelloKotlin5_1 {
}

8 协程的超时

/**
 * 协程的超时 设置withTimeout
 * 我们在使用协程时,如果取消了协程,那么很大一部分原因在于协程的执行时间超过了指定时间:我们可以通过手工引用与协程对应的Job的
 * 方式来启动一个单独的协程用于取消这个协程,不过Kotlin提供了一个内建的函数帮我们更方便的实现这一点(withTimeout)
 */

fun main() = runBlocking {
    try {
        withTimeout(1900) {
            repeat(5) {// 修改为4 就不会抛出TimeoutCancellationException
                    i ->
                println("hello $i")
                delay(400)
            }
        }
    } catch (ex: TimeoutCancellationException) {

    }
    withTimeout(1900){
        repeat(5){// 修改为4 就不会抛出TimeoutCancellationException
            i ->
            println("hello $i")
            delay(400)
        }
    }
}


class HelloKotlin6 {

}

9 协程的超时2

/**
 * 协程的超时设置withTimeoutOrNull
 * 由withTimeout函数所抛出的TimeoutCancellationException是CancellationException的子类,当CancellationException异常
 * 抛出时,我们并未在控制台上看到异常信息,因为在取消的协程中,CancellationException被认为是一种协程完成的正常原因
 * 既然CancellationException仅仅是一个异常而已 所有的资源都会以通常的方式来关闭,那么我们可以将相关的代码放到一个try...catch块中;
 * try {
 *     withTimeout(xxx) {
 *        ...
 *     }
 * }
 * catch (ex: TimeoutCancellationException) {
 * }
 *
 * 此外,相对于withTimeout Kotlin还提供了一个更便利的方法withTimeoutNotNull
 * 从功能角度上看 他非常类似withTimeout,不过当超时发生时,它不会抛出CancellationException异常 而是直接返回null
 *
 */
fun main() = runBlocking {
    val res = withTimeoutOrNull(1900){
        repeat(4){// 修改为4 就不会抛出TimeoutCancellationException
                i ->
            println("hello $i")
            delay(400)
        }
        "haha" // 协程没有超时 则res会被赋值为“haha” 否则 res等于null
    }

    println("result is $res")
}

class HelloKotlin7 {
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值