【问题标题】:Cancelling a non-cancelable coroutine in Kotlin在 Kotlin 中取消不可取消的协程
【发布时间】:2020-12-10 11:41:59
【问题描述】:

考虑一下这个不可取消的协程,顾名思义。

fun main(args: Array<String>) = runBlocking {

    val nonCancellableJob = launch(Dispatchers.Default) {
        for (i in 1..1000) {
            if (i % 100 == 0) {
                println("Non cancellable iteration $i")
            }
        }
    }

    println("Cancelling non cancellable job...")
    nonCancellableJob.cancelAndJoin()
}

现在,如果我摆脱显式调度程序Dispatchers.Default 并使用继承的调度程序,即launch {...},协程将立即取消而不打印任何内容。似乎取消了一个非取消协程!是bug还是什么?

【问题讨论】:

    标签: kotlin kotlin-coroutines


    【解决方案1】:

    当您不使用 Dispatchers.Default 时,launch 将继承 runBlocking 的调度程序,这似乎会推迟执行直到需要。

    即使您的协程在运行时无法取消(因为它没有任何活动检查也没有暂停点),仍然可以在它开始执行之前将其取消。

    您可以修改您的启动构建器的CoroutineStart 以更改此行为:

    声明后立即执行:

    val nonCancellableJob = launch(start = CoroutineStart.UNDISPATCHED) { ... }
    

    为了防止在开始之前取消:

    val nonCancellableJob = launch(start = CoroutineStart.ATOMIC) { ... }
    

    【讨论】:

    • 谢谢。这是否意味着理论上有可能在不修改其上下文的start 元素的情况下启动和执行协程?
    • 仅在理论上,实际上runBlocking 的调度程序不会执行您的launch,直到它到达主runBlocking 块中的暂停点。
    • 是的,我明白了。在println 前面添加delay(10),强制runBlocking 调度程序启动并执行协程。再次感谢。
    【解决方案2】:

    继承的调度程序在您启动它的同一线程上运行。这使得runBlocking 建立了一个顶级循环,该循环遍历其中运行的协程,并一一恢复它们。在当前协程自行挂起之前,它无法恢复下一个协程。

    在您的代码中,顶层 runBlocking 协程创建了一个子协程,然后在没有暂停的情况下继续做一些其他事情:打印一条消息并取消它刚刚创建的协程。此时,协程仍处于创建但未运行的边缘。您的cancelAndJoin 调用在此状态下取消它,然后它有机会永远占用runBlocking 调度程序。它立即将状态更改为“通过取消完成”,整个程序结束。

    【讨论】:

      猜你喜欢
      • 2021-10-02
      • 1970-01-01
      • 1970-01-01
      • 2021-01-04
      • 2021-10-01
      • 2023-02-03
      • 1970-01-01
      • 2020-03-23
      • 1970-01-01
      相关资源
      最近更新 更多