CoroutineScope.async 上的异常传播如何工作?

2024-04-19

我看到多个消息来源声称 async{} 块内发生的异常不会传递到任何地方,仅存储在Deferred实例。声称该异常仍然是“隐藏的”,并且仅在人们调用时影响外部事物await()。这通常被描述为之间的主要区别之一launch{} and async{}. 这是一个例子 https://stackoverflow.com/a/46226519/7441319.

异步代码中未捕获的异常存储在 由此产生的 Deferred 并且不会交付到其他任何地方,它将得到 除非经过处理,否则会默默丢弃

根据这一说法,至少按照我的理解,以下代码不应该抛出异常,因为没有人调用await:

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

然而,异常还是被抛出了。这个也有讨论here https://github.com/Kotlin/kotlinx.coroutines/issues/2404#issuecomment-732298335,但我无法真正理解why通过阅读本文。

所以在我看来,当异步抛出时,“取消信号”会在父作用域上传播,即使await()没有被调用。也就是说,异常并没有真正隐藏起来,也没有默默地被丢弃,正如上面引用的那样。我的假设正确吗?

现在,如果我们通过SupervisorJob(),代码确实not throw:

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

这似乎是合理的,因为主管的工作就是要承受失败。

现在是我完全不明白的部分。如果我们通过Job(),代码仍然运行而不抛出,即使Job()应该将失败传播到其父范围:

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

所以我的问题是,为什么不传递 Job 会抛出异常,但传递 Job 或 SupervisorJob 不会抛出异常?


从某种意义上说,您遇到的混乱是 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吞掉异常。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

CoroutineScope.async 上的异常传播如何工作? 的相关文章

随机推荐

  • 什么是延迟加载?

    什么是延迟加载 阅读一些答案后编辑 为什么人们如此频繁地使用这个术语 假设您只是使用 ASP ADO 记录集并使用数据或 ADO NET 数据源加载它以用于网格视图 我想我应该问为什么人们使用延迟加载这个术语 他们的 其他 类型是什么 之所
  • Xcode 10 iOS firebase firestore SDK - 多个命令在 Firebase 中产生 gRPCCertificates.bundle 错误

    我收到多个命令生成 gRPCCertificates bundle 多个命令生成 iphoneos gRPCCertificates bundle 1 目标 gRPC C gRPCCertificates 具有创建目录命令 输出 iphon
  • 删除ON DELETE CASCADE

    我有一张儿童桌 创建表时 外键带有 ON DELETE CASCADE 子表或父表中都没有记录 我希望主键 外键保持原样 但只想从子表中删除 CASCADING 选项 无论如何 我可以改变那个子表吗 谢谢 桌子 SHOW CREATE TA
  • 使用变量在存在索引的情况下进行 MySQL 排名

    使用 N N 1 的经典技巧来获取某些有序列上的项目的排名 现在 在订购之前 我需要通过将基表与其他表进行内部连接来过滤掉基表中的一些值 所以查询看起来像这样 SET N 0 SELECT N N 1 AS rank fa id fa va
  • 修复小数的浮点精度

    a 1 for x in range 5 a 0 1 print a 这是结果 1 1 1 2000000000000002 1 3000000000000003 1 4000000000000004 1 5000000000000004
  • 如何在 SQL 中选择一个月的第一天?

    如何选择给定 DateTime 变量所在月份的第一天 我知道使用这种代码很容易做到 select CAST CAST YEAR mydate AS VARCHAR 4 CAST MONTH mydate AS VARCHAR 2 01 AS
  • WebSphere 7+ 中的 JSF 2 Mojarra 和 Primefaces

    我一直在与 WebSphere 7 和 Mojarra 2 0 4 的邪恶联盟作斗争 并且遇到了一些麻烦 我按照此论坛帖子中指定的说明进行操作 并正确初始化了 Mojarra 2 0 4 基本上 我创建了一个独立的类加载器共享库 该库从外部
  • 如何在 R 中设置 highchart 全局选项

    我在 javascript 中看到了很多示例 但我找不到在 R 中执行此操作的示例 这是 API 链接 http api highcharts com highcharts global http api highcharts com hi
  • WIQL 查询获取项目中的所有团队和用户?

    let s say i have a project name Scrum and that has some users the project and got sprints so i want dstinct users of the
  • HttpConfiguration.get_ServiceResolver() 丢失

    我正在将 Ninject 与 MVC4 连接起来 并使其工作到尝试实际解决依赖关系的程度 但是 我收到以下异常 找不到方法 System Web Http Services DependencyResolver System Web Htt
  • Java 构造函数内部的多态性

    Code class A A test void test System out println from A class B extends A void test System out println from B class C pu
  • Google App Engine 的简单用户管理示例?

    我是 Google App Engine 的新手 当我学习本教程时 我发现我们在 php mysql 中所做的一些事情在 GAE 中不可用 例如 在 dataStore 中 自动增量功能不可用 我也对 GAE 中的会话管理感到困惑 总的来说
  • 在对象为布尔值的情况下,Javascript 中如何进行类型强制转换?

    尽我所知 x false 应该做同样的事情 x 因为他们都试图解释x作为布尔值 然后对其求反 然而 当我尝试对此进行测试时 我开始出现一些极其奇怪的行为 例如 false and false 两者都返回 true 此外 false unde
  • 内核虚拟地址转换

    考虑到 Linux 和 32 位 x86 架构 可访问的 4GB 地址空间的比例为 3 1 用户空间分配0 3 Gb 而3 4 Gb 分配给内核 大于 3Gb 且位于内核地址空间的虚拟地址如何转换为物理地址 页表会出现吗 梅尔 戈尔曼的书中
  • 如何列出泽西岛所有注册的 JAX-RS 实体提供商

    假设我有一个带有嵌入式码头的简单球衣应用程序github上的演示项目 https github com varren jersey2 gradle embedded jetty demo repo和下面的基本代码 回到 jersey1 的日
  • Android Studio 在运行测试时不使用 Gradle jvmArgs?

    我有一个带有 JUnit 测试的 Android 库 只有在使用 noverify标志传递给 JVM 我相信是因为this https stackoverflow com a 15122960 1910286 如果从命令行运行测试就会通过
  • 当我有表面声明时,如何为 sm_1X 和 sm_2X 编译 CUDA 程序

    我正在编写一个使用表面 重新采样并写入纹理 来提高性能的库 surface
  • 每行熊猫都会在下周日返回

    在 Pandas for Python 中 我有一个数据集 其中有一列日期时间 我需要创建一个新列 其中每行包含下周日的日期 我尝试过各种方法尝试使用 iterrows 然后计算出一周中的某一天 并添加一天直到该天为 7 但它没有起作用 我
  • GNUPLOT 绘制 5 天的财务周

    我已经寻找了一段时间 以找出如何从财务图中删除一周中的几天 但没有成功 我需要该图仅包含一周中的几天并完全忽略周末 以便财务图表中没有 2 天的差距 我有 CSV 格式的开盘 低 收盘 高数据 并且缺少周末数据 它绘制得很好 但我找不到如何
  • CoroutineScope.async 上的异常传播如何工作?

    我看到多个消息来源声称 async 块内发生的异常不会传递到任何地方 仅存储在Deferred实例 声称该异常仍然是 隐藏的 并且仅在人们调用时影响外部事物await 这通常被描述为之间的主要区别之一launch and async 这是一