美文网首页
Kotlin-协程的取消关键技术分析

Kotlin-协程的取消关键技术分析

作者: 蒋斌文 | 来源:发表于2021-06-15 12:08 被阅读0次

    Kotlin-协程的取消关键技术分析

    image-20210615111549352
    fun main() = runBlocking {
        val myJob = GlobalScope.launch {
            repeat(200) { i ->
                println("hello: $i")
    
                delay(500)
    
            }
        }
    
        delay(1100)
        println("hello world")
    
        myJob.cancel()
        myJob.join()
    
    
        println("welcome")
    }
    

    RUN> 🏄🏄🏄🏄🏄🏄

    hello: 0
    hello: 1
    hello: 2
    hello world
    welcome
    
    Process finished with exit code 0
    
    image-20210615112101098

    下面来瞅一下它的cancel()方法的说明:

    image-20210615112244628
    public fun cancel(cause: CancellationException? = null)
    cause: CancellationException? = null 可空类型,默认值为null
    
    Thrown by cancellable suspending functions if the Job of the coroutine is cancelled while it is suspending. It indicates normal cancellation of a coroutine. It is not printed to console/log by default uncaught exception handler. See CoroutineExceptionHandler
    public actual typealias CancellationException = java.util.concurrent.CancellationException
    
    image-20210615112533715

    可以显示的指定一下这个参数,如下:

    fun main() = runBlocking {
        val myJob = GlobalScope.launch {
            repeat(200) { i ->
                println("hello: $i")
    
                delay(500)
    
            }
        }
    
        delay(1100)
        println("hello world")
    
        myJob.cancel(CancellationException("just a try"))
        myJob.join()
    
    
        println("welcome")
    }
    
    myJob.cancel(CancellationException("just a try"))
    myJob.join()
    这俩一定是成对的出现,其中为啥一定得要调用join()
    是因为Cancel调用之后其协程并不会立马就取消,
    所以这个join0需要等待一下协程彻底取消
    

    那既然这俩是需要成对来编写的,那有没有一种简化的方法能代替上面两句代码呢?答案是肯定的,如下:

    fun main() = runBlocking {
        val myJob = GlobalScope.launch {
            repeat(200) { i ->
                println("hello: $i")
    
                delay(500)
    
            }
        }
    
        delay(1100)
        println("hello world")
    
        myJob.cancelAndJoin()
    
    
        println("welcome")
    }
    
    /**
    Cancels the job and suspends the invoking coroutine until the cancelled job is complete.
    This suspending function is cancellable and always checks for a cancellation of the invoking coroutine's Job. If the Job of the invoking coroutine is cancelled or completed when this suspending function is invoked or while it is suspended, this function throws CancellationException.
    In particular, it means that a parent coroutine invoking cancelAndJoin on a child coroutine that was started using launch(coroutineContext) { ... } builder throws CancellationException if the child had crashed, unless a non-standard CoroutineExceptionHandler is installed in the context.
    This is a shortcut for the invocation of cancel followed by join.
    */
    public suspend fun Job.cancelAndJoin() {
        cancel()
        return join()
    }
    

    取消任务并且挂起这个调用的协程直到取消的任务真正的执行完。

    image-20210615115128324

    kotlinx.coroutines包下的所有挂起函数都是可取消的,他们会检查协程的取消状态,当取消时就会抛出CancellationException异常。不过,如果协程正在处于某个计算过程当中,并且没有检查取消状态,那么它就是无法被取消的。

    fun main() = runBlocking {
        val startTime = System.currentTimeMillis()
        val job = launch(Dispatchers.Default) {
            var nextPrintTime = startTime
    
            var i = 0
    
            while (i < 20) {
                if (System.currentTimeMillis() >= nextPrintTime) {//CPU空转轮询
                    println("job: I am sleeping ${i++}")
                    nextPrintTime += 500L
                }
            }
        }
    
        delay(1300)
        println("hello world")
    
        job.cancelAndJoin()
        println("welcome")
    }
    

    RUN> 🏄🏄🏄🏄🏄🏄

    job: I am sleeping 0
    job: I am sleeping 1
    job: I am sleeping 2
    hello world
    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
    welcome
    
    Process finished with exit code 0
    

    呃,居然协程木有取消成功,这是为啥呢?其实这里要论证的就是刚才的这个理论:

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

    那怎么能让其正常取消呢?这里又得先来看一下理论:

    image-20210615120019312

    有两种方式可以让计算代码变为可取消的:

    1、周期性地调用了一个挂起函数,该挂起函数会检测取消状态,比如说使用yield函数。

    2、显示地检查取消状态。

    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: I am sleeping ${i++}")
                    nextPrintTime += 500L
                }
            }
        }
    
        delay(1300)
        println("hello world")
    
        job.cancelAndJoin()
        println("welcome")
    }
    

    RUN> 🏄🏄🏄🏄🏄🏄

    job: I am sleeping 0
    job: I am sleeping 1
    job: I am sleeping 2
    hello world
    welcome
    
    Process finished with exit code 0
    
    @Suppress("EXTENSION_SHADOWED_BY_MEMBER")
    public val CoroutineScope.isActive: Boolean
        get() = coroutineContext[Job]?.isActive ?: true
    // CoroutineScope的一个扩展属性
    
    image-20210615115810054

    第一个程序delay(500) 为什么可以被取消

    delay(500)
    
    image-20210615120458109

    相关文章

      网友评论

          本文标题:Kotlin-协程的取消关键技术分析

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