如何取消 kotlin 协程并确保它已被取消

问题描述 投票:0回答:1

我如何确保协程确实被取消,而不是只是返回或通过其他方式取消?

检查作业状态和调用

cancel()
之间存在明显的竞争条件。我怎么知道它实际上已被取消?

val job = CoroutineScope(Dispatchers.Default).async { delay(1) }
if (!job.isCompleted)
  job.cancel()
kotlin concurrency coroutine cancellation
1个回答
0
投票

也许有更好的方法,但一种方法是取消具有非常特定异常的作业,然后检查它是否因完全相同的异常而被取消。如果像您的示例中那样使用

Deferred
,这相当容易:

suspend fun main(): Unit = coroutineScope {
    val job = GlobalScope.async {
        delay(1.seconds)
        println("Done")
    }
    launch {
        delay(1.seconds)
        println("Cancelled #1: " + job.cancelAndCheck())
    }
    launch {
        delay(1.seconds)
        println("Cancelled #2: " + job.cancelAndCheck())
    }
}

suspend fun Deferred<*>.cancelAndCheck(): Boolean {
    val e = CancellationException()
    cancel(e)
    join()
    return getCompletionExceptionOrNull() === e
}

至少在我的机器上,我有时看到它被#1取消,有时被#2取消,有时被无取消(成功完成)。由于这是一种竞争条件,因此可能很难甚至不可能在其他计算机上重现。

令人惊讶的是,我没有看到类似的 API

Job
。显然,我们无法从
Job
得到结果,但我不明白为什么不能在
getCompletionExceptionOrNull()
级别添加
Job
,而不是
Deferred
。我发现的唯一方法是使用
invokeOnCompletion
,但如果感觉有点 hacky:

suspend fun main(): Unit = coroutineScope {
    val job = GlobalScope.launch {
        delay(1.seconds)
        println("Done")
    }
    launch {
        delay(1.seconds)
        println("Cancelled #1: " + job.cancelAndCheck())
    }
    launch {
        delay(1.seconds)
        println("Cancelled #2: " + job.cancelAndCheck())
    }
}

suspend fun Job.cancelAndCheck(): Boolean {
    val e = CancellationException()
    cancel(e)
    return suspendCancellableCoroutine { cont ->
        val handle = invokeOnCompletion { cont.resume(it === e) }
        cont.invokeOnCancellation { handle.dispose() }
    }
}

这两种解决方案可能都需要针对各种情况进行额外的测试,例如不可取消的工作等。

© www.soinside.com 2019 - 2024. All rights reserved.