Kotlin-协程的取消关键技术分析
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
下面来瞅一下它的cancel()方法的说明:
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
可以显示的指定一下这个参数,如下:
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()
}
取消任务并且挂起这个调用的协程直到取消的任务真正的执行完。
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
呃,居然协程木有取消成功,这是为啥呢?其实这里要论证的就是刚才的这个理论:
如果协程正在处于某个计算过程当中,并且没有检查取消状态,那么它就是无法被取消的
那怎么能让其正常取消呢?这里又得先来看一下理论:
有两种方式可以让计算代码变为可取消的:
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的一个扩展属性
第一个程序delay(500)
为什么可以被取消
delay(500)