我如何确保协程确实被取消,而不是只是返回或通过其他方式取消?
检查作业状态和调用
cancel()
之间存在明显的竞争条件。我怎么知道它实际上已被取消?
val job = CoroutineScope(Dispatchers.Default).async { delay(1) }
if (!job.isCompleted)
job.cancel()
也许有更好的方法,但一种方法是取消具有非常特定异常的作业,然后检查它是否因完全相同的异常而被取消。如果像您的示例中那样使用
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() }
}
}
这两种解决方案可能都需要针对各种情况进行额外的测试,例如不可取消的工作等。