kotlinKotlin-CoroutinesAndroid开发

Kotlin学习笔记(十八)协程(取消与超时)

2020-10-30  本文已影响0人  大虾啊啊啊

1.取消协程执行

package com.example.kotlin01

import kotlinx.coroutines.cancelAndJoin
import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch

suspend fun main() {
    //构建作用区域
    coroutineScope {
        var job = launch {
            repeat(100)
            {
                println("这是第 $it 次")
                //非阻塞等待500毫秒
                delay(500)
            }
        }
        //非阻塞等待1300毫秒
        delay(1300)
        println("我已等待结束....")
        job.cancel()
        job.join()
       // job.cancelAndJoin()
    }


}



这是第 0 次
这是第 1 次
这是第 2 次
我已等待结束....

通过coroutineScope构建作用区域,并执行协程,协程体每隔开500毫秒就打印一次。接着再协程体外面非阻塞等待1300毫秒后调用cancel和join方法取消协程的执行。cancel代码取消协程的执行,这里质之所以还要调用join方法,是因为join意思是阻塞等待协程结束。也就是说cancel执行后会马上返回,执行后续的代码,但是这个时候协程不一定结束。再调用join方法,这里表示阻塞等待协程结束。确保协程结束之后才执行后续的代码。我们也可以调用job.cancelAndJoin()

2.取消操作是协作完成的

协程的取消操作是协作完成的,也就是协程必须协作才能取消。kotlinx.coroutines中的挂起函数在运行时会检查协程是否被取消了,挂起函数可以取消协程的执行,并在取消的时候抛出CancellationException 。假如协程正在执行任务,如果没有检查协程是否是处于取消状态的话,则无法取消协程的执行。

package com.example.kotlin01

import kotlinx.coroutines.cancelAndJoin
import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch

suspend fun main() {
    //构建作用区域
    coroutineScope {
        var job = launch {
            var time = System.currentTimeMillis()
            var i = 0
            while (i < 5) {
                if (System.currentTimeMillis() >= time) {
                    println("这是第 ${++i} 次")
                    time += 500
                }
            }
        }
        //非阻塞等待1300毫秒
        delay(1300)
        println("我已等待结束....")
        job.cancel()
        job.join()
        // job.cancelAndJoin()
    }


}

这是第 1 次
这是第 2 次
这是第 3 次
我已等待结束....
这是第 4 次
这是第 5 次

以上的例子中,尽管在1300毫秒之后取消了协程执行,但是协程体里还是在继续在执行,打印后续的第四次和第五次。这说明没有挂起函数检查协程的状态的时候,是无法取消协程的。

3.使计算代码可取消(也就是使协程体里的代码可以取消)

通过挂起函数检查协程的状态,yieid()函数是一个好选择。也可以通过isActive来解决,isActive意思是判断当前协程是否是活跃可用的状态。

1.使用isActive使计算代码可以取消

package com.example.kotlin01

import kotlinx.coroutines.*

suspend fun main() {
    //构建作用区域
    coroutineScope {
        var job = launch {
            var time = System.currentTimeMillis()
            var i = 0
            while (isActive) {
                if (System.currentTimeMillis() >= time) {
                    println("这是第 ${++i} 次")
                    time += 500
                }
            }
        }
        //非阻塞等待1300毫秒
        delay(1300)
        println("我已等待结束....")
        job.cancel()
        job.join()
        // job.cancelAndJoin()
    }


}

这是第 1 次
这是第 2 次
这是第 3 次
我已等待结束....

2.使用yield()使计算代码可以取消

package com.example.kotlin01

import kotlinx.coroutines.*

suspend fun main() {
    //构建作用区域
    coroutineScope {
        var job = launch {
            var time = System.currentTimeMillis()
            var i = 0
            while (i<5) {
                yield()
                if (System.currentTimeMillis() >= time) {
                    println("这是第 ${++i} 次")
                    time += 500
                }
            }
        }
        //非阻塞等待1300毫秒
        delay(1300)
        println("我已等待结束....")
        job.cancel()
        job.join()

        // job.cancelAndJoin()
    }


}

这是第 1 次
这是第 2 次
这是第 3 次
我已等待结束....

4.用 finally 关闭资源(Closing resources with finally)

package com.example.kotlin01

import kotlinx.coroutines.*

suspend fun main() {
    //构建作用区域
    coroutineScope {
        var job = launch {
            try {
                repeat(1000){
                    println("这是一次打印")
                    delay(500)
                }
            }
            finally {
                println("finally")
            }
        }
        //非阻塞等待1300毫秒
        delay(1300)
        job.cancel()
        job.join()
        println("我已等待结束....")

        // job.cancelAndJoin()
    }
}

这是一次打印
这是一次打印
这是一次打印
finally
我已等待结束....

join和cancelAndJoin会等待所有的执行,回收操作执行完毕,才会执行后续的代码。

5.运行不可取消的代码块(Run non-cancellable block)

package com.example.kotlin01

import kotlinx.coroutines.*

suspend fun main() {
    //构建作用区域
    coroutineScope {
        var job = launch {
            try {
                repeat(1000){
                    println("这是一次打印")
                    delay(500)
                }
            }
            finally {
                delay(1000)
                println("finally")
            }
        }
        //非阻塞等待1300毫秒
        delay(2000)
        job.cancel()
        job.join()
        println("我已等待结束....")

        // job.cancelAndJoin()
    }


}



这是一次打印
这是一次打印
这是一次打印
这是一次打印
我已等待结束....

上面的例子finally并没有打印,因为我们在finally中调用了挂起函数,会检查协程的状态,会导致抛出CancellationException。因此此时协程已经被取消了。这是一个正常的逻辑。但是有时候我们需要在取消的协程中调用挂起函数。也就是我们需要打印出finally。可以通过withContext 和NonCancellable 解决

package com.example.kotlin01

import kotlinx.coroutines.*

suspend fun main() {
    //构建作用区域
    coroutineScope {
        var job = launch {
            try {
                repeat(1000){
                    println("这是一次打印")
                    delay(500)
                }
            }
            finally {
                //不取消
                withContext(NonCancellable){
                    delay(1000)
                    println("finally")
                }

            }
        }
        //非阻塞等待1300毫秒
        delay(2000)
        job.cancel()
        job.join()
        println("我已等待结束....")

        // job.cancelAndJoin()
    }


}



这是一次打印
这是一次打印
这是一次打印
这是一次打印
finally
我已等待结束....

6.超时(Timeout)

一般协程的取消的原因都是因为协程处理任务的时间已经超过我们预期的时间。我们可以进行手动取消,在官方的文档中也提供了withTimeout此类函数进行操作

package com.example.kotlin01

import kotlinx.coroutines.*

suspend fun main() {
    //构建作用区域
    coroutineScope {
        withTimeout(2000) {
            launch {
                repeat(1000) {
                    println("这是一次打印")
                    delay(500)
                }
            }

        }
    }

}
这是一次打印
这是一次打印
这是一次打印
这是一次打印
Exception in thread "main" kotlinx.coroutines.TimeoutCancellationException: Timed out waiting for 2000 ms
    at kotlinx.coroutines.TimeoutKt.TimeoutCancellationException(Timeout.kt:158)
    at kotlinx.coroutines.TimeoutCoroutine.run(Timeout.kt:128)
    at kotlinx.coroutines.EventLoopImplBase$DelayedRunnableTask.run(EventLoop.common.kt:497)
    at kotlinx.coroutines.EventLoopImplBase.processNextEvent(EventLoop.common.kt:274)
    at kotlinx.coroutines.DefaultExecutor.run(DefaultExecutor.kt:68)
    at java.lang.Thread.run(Thread.java:748)

上面我们看到2000时间超时之后,抛出TimeoutCancellationException异常。我们要不想要抛出异常,也可以使用withTimeoutOrNull

package com.example.kotlin01

import kotlinx.coroutines.*

suspend fun main() {
    //构建作用区域
    coroutineScope {
        withTimeoutOrNull(2000) {
            launch {
                repeat(1000) {
                    println("这是一次打印")
                    delay(500)
                }
            }

        }
    }

}
}
这是一次打印
这是一次打印
这是一次打印
这是一次打印
上一篇 下一篇

猜你喜欢

热点阅读