为什么延迟会使 co 例程可以取消

Vik*_*rya 1 kotlin kotlin-coroutines

fun main() = runBlocking {

    var i = 1

    var job = launch (Dispatchers.Default){

        println("Thread name  =  ${Thread.currentThread().name}")
        while (i < 10) { // replace i < 10 to isActive to make coroutine cancellable
            delay(500L)
//            runBlocking { delay(500L) }
            println("$isActive ${i++}")
        }
    }

    println("Thread name  =  ${Thread.currentThread().name}")
    delay(2000L) // delay a bit
    println("main: I'm tired of waiting!")
    job.cancelAndJoin() // cancels the job and waits for its completion
    println("main: Now I can quit.")

}
Run Code Online (Sandbox Code Playgroud)

输出

Thread name  =  main
Thread name  =  DefaultDispatcher-worker-1
true 1
true 2
true 3
main: I'm tired of waiting!
main: Now I can quit.
Run Code Online (Sandbox Code Playgroud)

如果我使用runBlocking { delay(500L) }那么上面的协同例程是不可取消的。因此,它将打印直到 的所有值9。

但是当我delay(500L)自动使用时,协同例程可以被取消。为什么 ?

Moi*_*ira 6

delay实际上它自己并没有做任何事情,它只是安排协程在稍后的时间点恢复。当然,可以随时取消延续。

runBlocking另一方面,实际上阻塞了一个线程(这就是为什么编译器会抱怨协程内的阻塞操作,以及为什么你永远不runBlocking应该在单元测试之外使用)。

注意:由于main现在可以是挂起函数,因此通常不需要在核心应用程序代码中使用它。

不应在协程
runBlocking中使用此函数

协程与线程一样,并不是真正可中断的;它们必须依赖于协作取消(另请参阅为什么停止线程不好)。这意味着取消实际上也不会执行任何操作;当您取消上下文时,它只是通知其所有子上下文自行取消,但仍在运行的任何代码将继续运行,直到达到取消检查为止。

重要的是要认识到这runBlocking 不是一个suspend函数。它无法暂停、恢复或取消。默认情况下,父上下文不会传递给它(它接收一个EmptyCoroutineContext),因此用于执行 的协程runBlocking不会对上游发生的任何事情做出反应。

当你写的时候

while (i < 10) {
    runBlocking { delay(500L) }
    println("$isActive ${i++}")
}
Run Code Online (Sandbox Code Playgroud)

这里没有可取消的操作。因此,代码从不检查其上下文是否已被取消,因此它将继续执行直到完成。

delay但是,是可以取消的;一旦其父上下文被取消,它就会立即恢复并抛出异常(即停止。)

看一下生成的代码:

@Nullable
public final Object invokeSuspend(@NotNull Object $result) {
    switch (this.label) {
    case 0:
        while (i.element < 10) {
            BuildersKt.runBlocking$default( ... );
            ...
            System.out.println(var3);
        }

        return Unit.INSTANCE;
    default:
        throw new IllegalStateException( ... );
    }
}
Run Code Online (Sandbox Code Playgroud)

对比一下这个

while (i.element < 10) {
    BuildersKt.runBlocking$default( ... );
    ...
    System.out.println(var3);
}
Run Code Online (Sandbox Code Playgroud)

和

do {
    ...
    System.out.println(var3);
    if (i.element >= 10) {
        return Unit.INSTANCE;
    }
    ...
} while (DelayKt.delay(500L, this) != var5);
Run Code Online (Sandbox Code Playgroud)

为了简洁起见,省略了变量声明和参数 ( ...)。


runBlocking如果当前线程被中断,则会提前终止,但这又是完全相同的协作机制,只不过它在线程级别而不是协程上运行。