从某种意义上说,您遇到的混乱是 Kotlin 协程在稳定之前取得早期成功的结果。在他们的实验日子里,他们缺乏的一件事是结构化并发,并且在这种状态下写了大量关于它们的网络材料(例如您的link 1 https://stackoverflow.com/a/46226519/7441319从 2017 年起)。一些当时有效的先入之见即使在人们成熟之后仍然存在,并且在最近的帖子中得到延续。
实际情况很清楚——你所需要了解的就是协程层次结构,它是通过Job
对象。是否是一个并不重要launch
or an async
,或任何进一步的协程构建器 - 它们的行为都是一致的。
考虑到这一点,让我们看一下您的示例:
runBlocking {
async { throw Exception("Oops") }
}
通过只写async
你隐式地使用了this.async
, where this
is the CoroutineScope
that runBlocking
已确立的。它包含Job
与关联的实例runBlocking
协程。为此,async
协程成为runBlocking
,所以后者会抛出异常async
协程失败。
runBlocking {
async(SupervisorJob()) { throw Exception("Oops") }
}
在这里,您提供一个没有父级的独立作业实例。这打破了协程层次结构runBlocking
不会失败。实际上,runBlocking
甚至不等待你的协程完成——添加一个delay(1000)
来验证这一点。
runBlocking {
async(Job()) { throw Exception("Oops") }
}
这里没有新的推理——Job
or SupervisorJob
, 没关系。您破坏了协程层次结构,并且故障不会传播。
现在让我们探索更多的变化:
runBlocking {
async(Job(coroutineContext[Job])) {
delay(1000)
throw Exception("Oops")
}
}
现在我们创建了一个新的Job
实例,但我们让它成为runBlocking
。这会引发异常。
runBlocking {
async(Job(coroutineContext[Job])) {
delay(1000)
println("Coroutine done")
}
}
与上面相同,但现在我们不抛出异常async
协程正常完成。它打印Coroutine done
,但随后发生了意想不到的事情:runBlocking
does not完成,程序将永远挂起。为什么?
这可能是这个机制中最棘手的部分,但一旦你仔细考虑一下,它仍然很有意义。当你创建一个协程时,它会在内部创建自己的协程Job
实例 - 无论您是否明确提供工作作为参数,这种情况总是会发生async
。如果您提供明确的工作,它将成为parent内部创造的工作。
现在,在第一种情况下,您没有提供明确的作业,父作业是由内部创建的作业runBlocking
。当它自动完成时runBlocking
协程完成。但是完成不会像取消那样传播到父级——您不希望仅仅因为一个子协程正常完成而所有事情都停止。
所以,当你创建自己的Job
实例并将其作为父级提供async
协程,你的工作还没有完成任何事情。如果协程失败,则失败会传播到您的作业,但如果正常完成,您的作业将永远保持在“正在进行”的原始状态。
最后,让我们引入SupervisorJob
again:
runBlocking {
async(SupervisorJob(coroutineContext[Job])) {
delay(1000)
throw Exception("Oops")
}
}
这会永远运行而没有任何输出,因为SupervisorJob
吞掉异常。