根据作业文档,在此作业上调用 [cancel][Job.cancel],但出现异常([CancellationException] 除外)也会取消父作业。 Job.cancel 函数仅接受 CancellationException。我正在测试这种行为,但取消子作业并不会取消父作业,尽管我没有使用 SupervisorJob。
/**
* Creates a job object in an active state.
* A failure of any child of this job immediately causes this job to fail, too, and cancels the rest of its children.
*
* To handle children failure independently of each other use [SupervisorJob].
*
* If [parent] job is specified, then this job becomes a child job of its parent and
* is cancelled when its parent fails or is cancelled. All this job's children are cancelled in this case, too.
* --The invocation of [cancel][Job.cancel] with exception (other than [CancellationException]) on this job also cancels parent.--
*
* Conceptually, the resulting job works in the same way as the job created by the `launch { body }` invocation
* (see [launch]), but without any code in the body. It is active until cancelled or completed. Invocation of
* [CompletableJob.complete] or [CompletableJob.completeExceptionally] corresponds to the successful or
* failed completion of the body of the coroutine.
*
* @param parent an optional parent job.
*/
@Suppress("FunctionName")
public fun Job(parent: Job? = null): CompletableJob = JobImpl(parent)
所以我的测试代码是这样的;
fun main() {
val parentJob = Job()
val scope = CoroutineScope(parentJob)
suspend fun printText(text: String) {
println("Before Delay: $text")
delay(1000)
println("After Delay: $text")
}
val job1 = scope.launch {
printText("Job#1")
}
job1.invokeOnCompletion {
println("Job#1 completed. Cause = $it")
}
val job2 = scope.launch {
printText("Job#2")
}
job2.invokeOnCompletion {
println("Job#2 completed. Cause = $it")
}
println(parentJob.children.joinToString { it.toString() } )
job1.cancel(CancellationException())
Thread.sleep(10000)
}
输出如下;
Before Delay: Job#1
Before Delay: Job#2
StandaloneCoroutine{Active}@462d5aee, StandaloneCoroutine{Active}@69b0fd6f
Job#1 completed. Cause = java.util.concurrent.CancellationException
After Delay: Job#2
Job#2 completed. Cause = null
Process finished with exit code 0
我的问题是为什么 job#2 没有被取消?
编辑:
在 @broot 和 @Steyrix 的回答之后。我扩展了测试用例。 现在,如果给定参数是“Job#1”,则 printText(string) 函数会抛出异常。所以我想在android中模拟一个viewmodel。假设我们创建了 CoroutineScope(Job()) 并且我提出了两个不同的请求。其中之一是抛出异常,但它被 try-catch block 捕获。因此其他作业继续执行其作业并且不会被取消。
现在的问题是 SupervisorJob 和 Job 之间有什么区别。为什么 viewmodelscope (CloseableCoroutineScope(SupervisorJob() + Dispatchers.Main.immediate)
) 使用 SupervisorJob ?
扩展示例;
fun main() {
val parentJob = Job()
val scope = CoroutineScope(parentJob)
suspend fun printText(text: String) {
println("Before Delay: $text")
if (text == "Job#1") {
throw IllegalArgumentException("Test")
}
delay(1000)
println("After Delay: $text")
}
val job1 = scope.launch {
try {
printText("Job#1")
} catch (e: Exception) {
println(e)
}
}
job1.invokeOnCompletion {
println("Job#1 completed. Cause = $it")
}
val job2 = scope.launch {
printText("Job#2")
}
job2.invokeOnCompletion {
println("Job#2 completed. Cause = $it")
}
println(parentJob.children.joinToString { it.toString() })
Thread.sleep(10000)
}
输出;
Before Delay: Job#1
java.lang.IllegalArgumentException: Test
Job#1 completed. Cause = null
Before Delay: Job#2
StandaloneCoroutine{Active}@462d5aee
After Delay: Job#2
Job#2 completed. Cause = null
Process finished with exit code 0
最佳答案
嗯,它的行为符合您引用的文档。您使用了 CancellationException
,因此它没有取消父级。
文档中唯一令人困惑的部分是为什么它提到使用 CancellationException
之外的其他异常类型进行取消。这不可能。 cancel()
用于正常取消,而不是失败,因此它永远不会传播到父级。
这看起来像是 Job()
函数文档中的一个小错误。
关于kotlin - 协程子作业取消,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/75157904/