协程的取消和超时
2020-11-14 本文已影响0人
码农修行之路
协程的取消和超时.png
取消协程
协程取消失效问题
- cancel()方法调用后 马上返回而不是等协程结束后再返回,所以协程并不一定马上停止 为了保证协程执行完再执行后续代码 此时就需要调用join()方法阻塞等待
fun main() = runBlocking {
val job = launch {
repeat(1000) {
println(" 打印低 $it 次 ")
delay(500L)
}
}
delay(2000L)
println(" 等待2秒后啦! ")
//job.cancel() // 取消协程
//job.join() // 阻塞 等待协程执行完毕
job.cancelAndJoin()
}
- 协程中执行循环操作 如果没有判读取消的条件 那么此协程是不会停止的
fun main() = runBlocking { val startTime = System.currentTimeMillis()
val job = launch(Dispatchers.Default) {
var i = 0
while (true) {
println(" 一直打印数据 ${i++} ")
}
}
delay(2000L)
job.cancelAndJoin()
}
解决取消协程失效问题:
- 添加判断循环停止的条件 while (i < 10)
- 添加挂起函数 只要下面调用job.cancelAndJoin() 所有的挂起都会被停止
while (true) {
delay(100L)
println(" 一直打印数据 ${i++} ")
}
- yieid()的使用
suspend fun main() {
val job = GlobalScope.launch {
var num = 0
while (true) {
//try {
yield()
//} catch (e: Exception) {// StandaloneCoroutine was cancelled
// println(" 捕捉的异常 ${e.message} ")
//}
println(" 开始执行到 ${num++} ")
}
}
println(" 取消前 是否还在活动 ${job.isActive} 是否取消 ${job.isCancelled} ")
job.cancelAndJoin()
println(" 取消后 是否还在活动 ${job.isActive} 是否取消 ${job.isCancelled} ")
delay(1000L)
}
- 添加是否取消或者是否活动的判断
while (isActive) {
println(" 开始执行到 ${num++} ")
}
########finally关闭资源
- 课取消的挂起函数在取消时会抛出异常 CancellationException 可以用常用的方式进行处理
例如:try {...} finally {...} 表达式和 kotlin 的 use 函数都可用于在取消协程时执行回收操作
fun main() = runBlocking {
val job = launch {
try {
repeat(1000) {
println(" 执行到第 $it ")
delay(500L)
}
} finally {
println(" finally 执行到 可以执行资源回收操作 ")
}
}
delay(1300L)
println("延迟等待结束")
// job.cancel()
//job.join()
// job.cancelAndJoin()// 等待所有启动的协程回收操作完成后再继续执行之后的代码
println("取消协程后")
}
运行不可取消的代码块
- 如果finally中使用挂起函数 将会抛出异常 CancellationException 因为此时协程已经被取消
也就是在finally中先调用挂起函数 会导致挂起函数之后的代码不会被执行 - 当然上述问题也并不是什么问题 因为良好的关闭操作通常都是非阻塞的且不会设计挂起函数 直接执行代码就好
如果非要添加挂起函数 那么就需要吧挂起函数包裹在 withContext(NonCancellable){...}代码块中 这样挂起函数后面的操作代码就能正常执行啦
fun main() = runBlocking {
val job = launch {
try {
repeat(1000) {
delay(200L)
println(" 执行到第 $it ")
}
} finally {
// 如果在finally中先调用挂起函数 会导致之后的输出不会执行
// 如果需要在取消的协程中调用挂起函数可以使用withContext(NonCancellable){...}代码块
/*delay(100L)
println(" finally 代码块执行 ")*/
withContext(NonCancellable) {
delay(100L)
println(" finally 代码块执行 ")
}
}
}
println("延迟执行前")
delay(2000L)
println("延迟执行后")
job.cancelAndJoin()
println("协程取消执行后")
}
超时
- 超时会抛出异常
Exception in thread "main" kotlinx.coroutines.TimeoutCancellationException: Timed out waiting for 1300 ms
fun main() = runBlocking {
withTimeout(1300L) {
repeat(1000) { i ->
println("I'm sleeping $i ...")
delay(500L)
}
}
}
怎样避免抛出异常:
- 可以通过try{...}catch(e:TimeoutCancellationException){...}代码块捕捉异常 进行处理
- 可以通过withTimeoutOrNull 函数以便在超时时返回 null 而不是抛出异常
fun main() = runBlocking {
val result = withTimeoutOrNull(1300L) {
repeat(1000) { i ->
println("I'm sleeping $i ...")
delay(500L)
}
}
println(" 结果 $result ")
}
var acquired = 0
class Resource {
init {
acquired++
} // Acquire the resource
fun close() {
acquired--
} // Release the resource
}
异步超时和资源
- withTimeout 是异步执行的 对于其块内代码 可能发生在任何时间 如果在 withTimeout() {} 返回之前 在块内打开或获取一些需要的在块外释放的资源
var acquired = 0
class Resource {
init { acquired++ } // Acquire the resource
fun close() { acquired-- } // Release the resource
}
fun main() {
runBlocking {
repeat(100_000) { // Launch 100K coroutines
launch {
val resource = withTimeout(60) { // Timeout of 60 ms
delay(50) // Delay for 50 ms
Resource() // Acquire a resource and return it from withTimeout block
}
resource.close() // Release the resource
}
}
}
// Outside of runBlocking all coroutines have completed
println(acquired) // Print the number of resources still acquired
}
上述例子 有可能还会获取到acquired > 0 的值 为什么呢?原因很简单: 就是资源创建和资源关闭有可能不同步
fun main() = runBlocking {
repeat(100_000) {
// Launch 100K coroutines
launch {
var resource: Resource? = null // Not acquired yet
try {
withTimeout(60) { // Timeout of 60 ms
delay(50) // Delay for 50 ms
resource = Resource() // Store a resource to the variable if acquired
}
// We can do something else with the resource here
} finally {
resource?.close() // Release the resource if it was acquired
}
}
}
// Outside of runBlocking all coroutines have completed
println(acquired) // Print the number of resources still acquired
}
上述例子就是把对资源的引用存储到变量 而不是从withTimeout块中返回资源 避免资源泄漏
谢谢亲们的关注支持 记得点赞哦!