程序员社区

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

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

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
Kotlin-协程的取消关键技术分析插图1
image-20210615112101098

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

Kotlin-协程的取消关键技术分析插图2
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
Kotlin-协程的取消关键技术分析插图3
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()
}

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

Kotlin-协程的取消关键技术分析插图4
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

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

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

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

Kotlin-协程的取消关键技术分析插图5
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的一个扩展属性
Kotlin-协程的取消关键技术分析插图6
image-20210615115810054

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

delay(500)
Kotlin-协程的取消关键技术分析插图7
image-20210615120458109
赞(0) 打赏
未经允许不得转载:IDEA激活码 » Kotlin-协程的取消关键技术分析

一个分享Java & Python知识的社区