I see multiple sources claiming that an exception happening inside an async{} block is not delivered anywhere and only stored in the Deferred instance. The claim is that the exception remains "hidden" and only influences things outside at the moment where one will call await(). This is often described as one of the main differences between launch{} and async{}. Here is an example.

异步代码中未捕获的异常存储在

According to this claim, at least the way I understand it, the following code should not throw, since no-one is calling await:

// throws
runBlocking {
  async { throw Exception("Oops") }
}

然而,异常会被抛出.这也是here年前讨论过的,但通过阅读这篇文章,我无法真正理解why.

所以在我看来,当异步抛出时,一个"取消信号"在父作用域上传播,即使await()没有被调用.正如上面引用的话所说,这个例外并没有真正隐藏起来,也没有悄悄地放弃.我的假设正确吗?

现在,如果我们通过SupervisorJob(),代码会抛出not:

// does not throw
runBlocking {
  async(SupervisorJob()) { throw Exception("Oops") }
}

This seems reasonable since supervisor job is meant to swallow failures.

And now comes the part I do not understand at all. If we pass Job(), the code still runs without throwing, even though Job() is supposed to propagate failures to its parent scope:

// does not throw. Why?
runBlocking {
  async(Job()) { throw Exception("Oops") }
}

所以我的问题是,为什么不通过任何一项工作都会丢球,但通过任何一项工作或监督工作都不会丢球?

推荐答案

In some sense, the mess you experience is a consequence of Kotlin coroutines having been an early success, before they became stable. In their experimental days, one thing they lacked was structured concurrency, and a ton of web material got written about them in that state (such as your link 1 from 2017). Some of the then-valid preconceptions remained with people even after their maturation, and got perpetuated in even more recent posts.

实际情况非常清楚-您所要理解的就是协程层次 struct ,它是通过Job个对象进行中介的.不管它是launch还是async,或者任何其他的协程构建器-它们的行为都是一致的.

With this in mind, let's go through your examples:

runBlocking {
  async { throw Exception("Oops") }
}

通过只写async,你隐式地使用了this.async,其中thisrunBlocking建立的CoroutineScope.它包含与runBlocking协同路由关联的Job实例.由于这个原因,async协同程序成为runBlocking的子程序,因此当async协同程序失败时,后者会抛出一个异常.

runBlocking {
  async(SupervisorJob()) { throw Exception("Oops") }
}

在这里,您提供了一个没有父级的独立作业(job)实例.这打破了协程层次 struct ,runBlocking不会失败.事实上,runBlocking件事甚至不需要等你的协同程序完成--加一个delay(1000)来验证这一点.

runBlocking {
  async(Job()) { throw Exception("Oops") }
}

这里没有新的理由——JobSupervisorJob,这无关紧要.你打破了协同程序的层次 struct ,失败不会传播.

Now let's explore a few more variations:

runBlocking {
    async(Job(coroutineContext[Job])) {
        delay(1000)
        throw Exception("Oops")
    }
}

现在,我们创建了一个新的Job实例,但我们将其设为runBlocking的子级.这会抛出一个异常.

runBlocking {
    async(Job(coroutineContext[Job])) {
        delay(1000)
        println("Coroutine done")
    }
}

Same as above, but now we don't throw an exception and the async coroutine completes normally. It prints Coroutine done, but then something unexpected happens: runBlocking does not complete, and the program hangs forever. Why?

这可能是这个机制中最棘手的部分,但一旦你仔细考虑,它仍然是非常有意义的.当您创建一个协同程序时,它会在内部创建自己的Job个实例——无论您是否明确地将作业(job)作为参数提供给async,这种情况都会发生.如果你提供了一个明确的工作,它将成为内部创建的工作的parent.

Now, in the first case, where you didn't provide an explicit job, the parent job is the one internally created by runBlocking. It automatically completes when the runBlocking coroutine completes. But completion doesn't propagate to the parent the way cancellation does — you wouldn't want everything stopping just because one child coroutine completed normally.

因此,当您创建自己的Job实例并将其作为async协程的父实例提供时,您的工作不会因任何事情而完成.如果协程失败,则失败会传播到您的作业(job),但是如果它正常完成,您的作业(job)将永远保持"正在进行"的原始状态.

最后,让我们再带来SupervisorJob个:

runBlocking {
    async(SupervisorJob(coroutineContext[Job])) {
        delay(1000)
        throw Exception("Oops")
    }
}

This just runs forever without any output, because SupervisorJob swallows the exception.

Kotlin相关问答推荐

测试Compose Multiplatform UI时,为另一个文件设置ComposeRule()

在KMM合成多平台中创建特定于平台的视图

无法访问类kotlin.coroutines.CoroutineContext';.判断模块类路径中是否存在丢失或冲突的依赖项

使用调度程序运行异步 Kotlin 代码

使用 kotlin 流删除 map 中具有某些相似性的值

Kotlin 中二叉树的深度

使用 Hilt 注入 CoroutineWorker

将 Kotlin 类属性设置器作为函数引用

Anko 中的水平线性布局

Gradle 同步失败:不支持的方法:KotlinPlatformContainer.supports()

Kotlin DataBinding 将静态函数传递到布局 xml

Hilt Activity 必须附加到 @AndroidEntryPoint 应用程序

将多个 Kotlin 流合并到一个列表中,而无需等待第一个值

在Kotlin中不带类直接引用枚举实例

如何将 Kotlin 日期中的字符串或时间戳格式化为指定的首选格式?

Kotlin协程无法处理异常

导航架构组件 - 未生成 DestinationFragmentArgs

将字符串转换为HashMap的最简单方法

是否可以在不使用class的情况下将 Mockito 与 Kotlin 一起使用?

RxJava2 UndeliverableException 在获取数据时发生方向变化