美文网首页Kotlin
协程的取消和超时

协程的取消和超时

作者: 码农修行之路 | 来源:发表于2020-11-14 21:40 被阅读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()
    }
    

    解决取消协程失效问题:

    1. 添加判断循环停止的条件 while (i < 10)
    2. 添加挂起函数 只要下面调用job.cancelAndJoin() 所有的挂起都会被停止
    while (true) {
        delay(100L)
        println(" 一直打印数据 ${i++} ")
    }
    
    1. 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)
    }
    
    1. 添加是否取消或者是否活动的判断
    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)
            }    
        }    
    }
    

    怎样避免抛出异常:

    1. 可以通过try{...}catch(e:TimeoutCancellationException){...}代码块捕捉异常 进行处理
    2. 可以通过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块中返回资源 避免资源泄漏
    谢谢亲们的关注支持   记得点赞哦!

    相关文章

      网友评论

        本文标题:协程的取消和超时

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