Alk*_*dis 8 async-await kotlin kotlin-coroutines
我看到多个消息来源声称 async{} 块内发生的异常不会传递到任何地方,仅存储在实例中Deferred
。据称,该异常仍然是“隐藏的”,并且仅在调用时影响外部事物await()
。launch{}
这通常被描述为和之间的主要区别之一async{}
。这是一个例子。
异步代码中未捕获的异常存储在生成的 Deferred 中,并且不会传递到其他任何地方,除非进行处理,否则它将被静默丢弃
根据这一说法,至少按照我的理解,以下代码不应该抛出异常,因为没有人调用await:
// throws
runBlocking {
async { throw Exception("Oops") }
}
Run Code Online (Sandbox Code Playgroud)
然而,异常还是被抛出了。这里也讨论了这一点,但通过阅读本文我无法真正理解为什么。
所以在我看来,当异步抛出时,即使await()
没有被调用,也会在父作用域上传播“取消信号”。也就是说,异常并没有真正隐藏起来,也没有默默地被丢弃,正如上面引用的那样。我的假设正确吗?
现在,如果我们传递 a SupervisorJob()
,代码不会抛出:
// does not throw
runBlocking {
async(SupervisorJob()) { throw Exception("Oops") }
}
Run Code Online (Sandbox Code Playgroud)
这似乎是合理的,因为主管的工作就是要承受失败。
现在是我完全不明白的部分。如果我们传递Job()
,代码仍然会运行而不会抛出异常,即使Job()
应该将失败传播到其父作用域:
// does not throw. Why?
runBlocking {
async(Job()) { throw Exception("Oops") }
}
Run Code Online (Sandbox Code Playgroud)
所以我的问题是,为什么不传递 Job 会抛出异常,但传递 Job 或 SupervisorJob 不会抛出异常?
Mar*_*nik 12
从某种意义上说,您所经历的混乱是 Kotlin 协程在稳定之前就取得了早期成功的结果。在他们的实验日子里,他们缺乏的一件事是结构化并发,并且在这种状态下写了大量关于它们的网络材料(例如2017 年的链接 1)。一些当时有效的先入之见即使在人们成熟之后仍然存在,并且在最近的帖子中得到延续。
\n实际情况很清楚 \xe2\x80\x94 你所需要理解的就是协程层次结构,它是通过对象来中介的Job
。无论是 salaunch
还是 an async
,或者任何进一步的协程构建器 \xe2\x80\x94 ,它们的行为都是一致的。
考虑到这一点,让我们看一下您的示例:
\nrunBlocking {\n async { throw Exception("Oops") }\n}\n
Run Code Online (Sandbox Code Playgroud)\n通过只写async
你隐式使用的this.async
,哪里是this
建立的。它包含与协程关联的实例。因此,协程成为 的子级,因此当协程失败时后者会抛出异常。CoroutineScope
runBlocking
Job
runBlocking
async
runBlocking
async
runBlocking {\n async(SupervisorJob()) { throw Exception("Oops") }\n}\n
Run Code Online (Sandbox Code Playgroud)\n在这里,您提供一个没有父级的独立作业实例。这会破坏协程层次结构并且runBlocking
不会失败。事实上,runBlocking
甚至不需要等待你的协程完成 \xe2\x80\x94 添加 adelay(1000)
来验证这一点。
runBlocking {\n async(Job()) { throw Exception("Oops") }\n}\n
Run Code Online (Sandbox Code Playgroud)\n这里没有新的推理 \xe2\x80\x94Job
或SupervisorJob
,这并不重要。您破坏了协程层次结构,并且故障不会传播。
现在让我们探索更多的变化:
\nrunBlocking {\n async(Job(coroutineContext[Job])) {\n delay(1000)\n throw Exception("Oops")\n }\n}\n
Run Code Online (Sandbox Code Playgroud)\n现在我们创建了一个新Job
实例,但我们将其作为 的子实例runBlocking
。这会引发异常。
runBlocking {\n async(Job(coroutineContext[Job])) {\n delay(1000)\n println("Coroutine done")\n }\n}\n
Run Code Online (Sandbox Code Playgroud)\n与上面相同,但现在我们不会抛出异常并且async
协程正常完成。它打印Coroutine done
,但随后发生了一些意想不到的事情:runBlocking
没有完成,并且程序永远挂起。为什么?
这可能是这个机制中最棘手的部分,但一旦你仔细考虑一下,它仍然很有意义。当您创建协程时,它会在内部创建自己的Job
实例 \xe2\x80\x94,无论您是否明确提供作业作为参数,这种情况总是会发生async
。如果您提供显式作业,它将成为该内部创建的作业的父作业。
现在,在第一种情况下,您没有提供显式作业,父作业是由 内部创建的作业runBlocking
。当协程完成时它会自动完成runBlocking
。但是完成不会像取消那样传播到父协程 \xe2\x80\x94 你不会希望一切都停止只是因为一个子协程正常完成。
因此,当您创建自己的Job
实例并将其作为协程的父级提供时async
,您的工作并没有通过任何操作完成。如果协程失败,则失败会传播到您的作业,但如果正常完成,您的作业将永远保持在“正在进行”的原始状态。
最后,让我们引入SupervisorJob
再次引入:
runBlocking {\n async(SupervisorJob(coroutineContext[Job])) {\n delay(1000)\n throw Exception("Oops")\n }\n}\n
Run Code Online (Sandbox Code Playgroud)\n这会永远运行而没有任何输出,因为SupervisorJob
吞掉了异常。
归档时间: |
|
查看次数: |
722 次 |
最近记录: |