美文网首页
Kotlin 协程(二) -协程取消与超时

Kotlin 协程(二) -协程取消与超时

作者: 码路恒行 | 来源:发表于2022-02-19 21:37 被阅读0次

    协程一:Kotlin 协程 (一)

    在长时间运行的程序中,如果协程的执行结果不需要了,那么协程是可以取消的,使用Job,cancel()函数执行

    示例(1):

    fun main() = runBlocking {

        val job = launch {

            // repeat 函数是一个内联函数,repeat(500) 表示重复执行500次

            repeat(500) { i ->

                println("Hello $i")

                delay(500)

            }

        }

        delay(1800)

        println("World")

        // 取消协程该次操作

        job.cancel()

        // 可以在一个作业中等待另一个作业结束后再进行其他操作

        job.join() // 等待作业执行结束

        println("Main函数退出了...")

    }

    示例(1)执行结果:

    Hello 0

    Hello 1

    Hello 2

    Hello 3

    World

    Main函数退出了...

    通过示例(1)可以看出,如果协程没有取消,println("Hello $i")函数应该执行500次,而协程在延时1800毫秒之后取消了,所以println("Hello $i")函数只执行了4次,协程取消之后,函数就不再执行了

    Job的cancel()和join()函数可以合并成一个挂起函数cancelAndJoin(),该挂起函数的作用是取消当前作业,并等待作业执行结束.

    示例(2):

    fun main() = runBlocking {

        val job = launch {

            // repeat 函数是一个内联函数,repeat(500) 表示重复执行500次

            repeat(500) { i ->

                println("Hello $i")

                delay(500)

            }

        }

        delay(1800)

        println("World")

        // 取消当前作业,并等待作业执行结束

        job.cancelAndJoin()

        println("Main函数退出了...")

    }

    示例(2)执行结果:

    Hello 0

    Hello 1

    Hello 2

    Hello 3

    World

    Main函数退出了...

    在kotlinx.coroutines包下的所有挂起函数都是可被取消的.它们检查协程的取消,并在取消时抛出 CancellationException. 不过,如果协程正在执行计算任务,并且没有检查取消的话,那么它是不能被取消的

    示例(3):

    fun main() = runBlocking {

        // 开始时间

        val startTime = System.currentTimeMillis()

        val job = launch(Dispatchers.Default) {

            // 下一次打印时间

            var nextPrintTime = startTime

            var i = 0

            while (i < 10) { // 循环计算

                if (System.currentTimeMillis() >= nextPrintTime) {

                    println("job sleeping ${i++} ")

                    nextPrintTime += 500

                }

            }

        }

        delay(1800)

        println("Hello World")

        job.cancelAndJoin()

        println("Main函数退出了...")

    }

    示例(3)执行结果:

    job sleeping 0

    job sleeping 1

    job sleeping 2

    job sleeping 3

    Hello World

    job sleeping 4

    job sleeping 5

    job sleeping 6

    job sleeping 7

    job sleeping 8

    job sleeping 9

    Main函数退出了...

    通过示例(3)可以看到,在调用cancel()之后,还继续打印 job sleeping,直到运行结束.说明协程正在执行计算任务,并且没有检查取消的话,那么它是不能被取消的

    如果想取消正在计算的job,可以定期调用挂起函数来检查取消,如yield()函数.或者是调用显式的检查取消状态,如示例(4)

    示例4:

    fun main() = runBlocking {

        // 开始时间

        val startTime = System.currentTimeMillis()

        val job = launch(Dispatchers.Default) {

            // 下一次打印时间

            var nextPrintTime = startTime

            var i = 0

           while (isActive) { // 可以被取消的计算循环

                if (System.currentTimeMillis() >= nextPrintTime) {

                    println("job sleeping ${i++} ")

                    nextPrintTime += 500

                }

            }

        }

        delay(1800)

        println("Hello World")

        job.cancelAndJoin()

        println("Main函数退出了...")

    }

    示例(4)执行结果:

    job sleeping 0

    job sleeping 1

    job sleeping 2

    job sleeping 3

    Hello World

    Main函数退出了...

    通过示例(4)可以看到,现在循环被取消了.isActive 是一个可以被使用在 CoroutineScope 中的扩展属性

    job还可以在 finally 中释放资源

    示例(5):

    fun main() = runBlocking {

        val job = launch {

            // repeat 函数是一个内联函数,repeat(500) 表示重复执行500次

            try {

                repeat(500) { i ->

                    println("Hello $i")

                    delay(500)

                }

            } finally {

                println("执行了finally块...")

            }

        }

        delay(1800)

        println("World")

        // 取消当前作业,并等待作业执行结束

        job.cancelAndJoin()

        println("Main函数退出了...")

    }

    示例(5)执行结果:

    Hello 0

    Hello 1

    Hello 2

    Hello 3

    World

    执行了finally块...

    Main函数退出了...

    如果在使用finally取消job,且在finally中执行挂起函数,由于job作业被取消了,所以finally里面的挂起函数将不会被执行

    示例(6):

    fun main() = runBlocking {

        val job = launch {

            // repeat 函数是一个内联函数,repeat(500) 表示重复执行500次

            try {

                repeat(500) { i ->

                    println("Hello $i")

                    delay(500)

                }

            } finally {

                println("执行了finally块...")

                delay(500)

                println("在finally 中执行delay之后...")

            }

        }

        delay(1800)

        println("World")

        // 取消当前作业,并等待作业执行结束

        job.cancelAndJoin()

        println("Main函数退出了...")

    }

    示例(6)执行结果:

    Hello 0

    Hello 1

    Hello 2

    Hello 3

    World

    执行了finally块...

    Main函数退出了...

    从示例(6)的执行结果可以看出,的job作业被取消之后的挂起函数没有得到执行,如果需要挂起一个被取消的协程,可以将相应的代码包装在 withContext(NonCancellable) {……} 中,并使用 withContext 函数以及 NonCancellable 上下文

    示例(7):

    fun main() = runBlocking {

        val job = launch {

            // repeat 函数是一个内联函数,repeat(500) 表示重复执行500次

            try {

                repeat(500) { i ->

                    println("Hello $i")

                    delay(500)

                }

            } finally {

                withContext(NonCancellable){

                    println("执行了finally块...")

                    delay(500)

                    println("在finally 中执行delay之后...")

                }

            }

        }

        delay(1800)

        println("World")

        // 取消当前作业,并等待作业执行结束

        job.cancelAndJoin()

        println("Main函数退出了...")

    }

    示例(7)执行结果:

    Hello 0

    Hello 1

    Hello 2

    Hello 3

    World

    执行了finally块...

    在finally 中执行delay之后...

    Main函数退出了...

    在使用协程的过程中,绝大多数取消一个协程的原因是因为它有可能超时.当你手动追踪一个相关 Job 的引用并启动了一个单独的协程在延迟后取消追踪,这里已经准备好使用 withTimeout 函数来做这件事

    示例(8):

    fun main() = runBlocking {

        withTimeout(1800){

            repeat(1000) {

                i ->

                println("Hello $i")

                delay(400)

            }

        }

    }

    示例(8)执行结果:

    hello 0

    hello 1

    hello 2

    hello 3

    hello 4

    Exception in thread "main" kotlinx.coroutines.TimeoutCancellationException: Timed out waiting for 1900 ms

        at kotlinx.coroutines.TimeoutKt.TimeoutCancellationException(Timeout.kt:116)

        at kotlinx.coroutines.TimeoutCoroutine.run(Timeout.kt:86)

        at kotlinx.coroutines.EventLoopImplBase$DelayedRunnableTask.run(EventLoop.common.kt:492)

        at kotlinx.coroutines.EventLoopImplBase.processNextEvent(EventLoop.common.kt:271)

        at kotlinx.coroutines.DefaultExecutor.run(DefaultExecutor.kt:68)

        at java.lang.Thread.run(Thread.java:748)

    从示例(8)可以看到,由withTimeout函数调用所抛出的TimeoutCancellationException异常是CancellationException的子类,当该异常抛出时,我们并未在控制台上看到整个异常堆栈信息,这是因为我们取消的协程当中,CancellationException被认为是一种协程完成的正常原因而已

    如果你需要做一些各类使用超时的特别的额外操作,可以使用类似 withTimeout 的 withTimeoutOrNull 函数,并把这些会超时的代码包装在 try {...} catch (e: TimeoutCancellationException) {...} 代码块中,而 withTimeoutOrNull 通过返回 null 来进行超时操作,从而替代抛出一个异常:

    示例(9):

    fun main() = runBlocking {

        val result = withTimeoutOrNull(1800){

            repeat(1000) {

                i ->

                println("Hello $i")

                delay(300)

            }

            "Hello World"

        }

        println("Result is $result")

    }

    示例(9)执行结果:

    Hello 0

    Hello 1

    Hello 2

    Hello 3

    Hello 4

    Hello 5

    Result is null

    相关文章

      网友评论

          本文标题:Kotlin 协程(二) -协程取消与超时

          本文链接:https://www.haomeiwen.com/subject/llaplrtx.html