异常传播如何在CoroutineScope e.async上工作? [英] How does exception propagation works on CoroutineScope.async?

查看:12
本文介绍了异常传播如何在CoroutineScope e.async上工作?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我看到多个来源声称发生在Async{}块内的异常不会被传递到任何地方,而只存储在Deferred实例中。主张的是,例外仍然是隐藏的,并且只影响人们将调用await()的时刻的外部事物。这通常被描述为launch{}async{}之间的主要区别之一。Here is an example

异步代码内部的未捕获异常存储在 结果被推迟,并且不在其他地方交付,它将获得 静默丢弃,除非已处理

根据这个声明,至少在我的理解中,下面的代码不应该抛出,因为没有人在调用等待:

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

但是,异常被抛出。here也讨论了这一点,但通过阅读本文,我无法真正理解为什么

因此,在我看来,当引发异步时,即使没有调用await(),取消信号也会在父作用域上传播。也就是说,这一例外并不是真的隐藏起来,也不是像上面的引语所说的那样默默放弃。我的假设正确吗?

现在,如果我们传递SupervisorJob(),代码不会抛出:

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

这似乎是合理的,因为主管工作意味着要接受失败。

现在到了我完全不明白的部分。如果我们传递Job(),即使Job()应该将故障传播到其父作用域:

,代码仍然运行而不引发
// does not throw. Why?
runBlocking {
  async(Job()) { throw Exception("Oops") }
}

所以我的问题是,为什么传递任何作业都不会引发异常,但传递作业或主管作业都不会引发异常?

推荐答案

在某种意义上,您所经历的混乱是kotlin协程在变得稳定之前就已经取得早期成功的结果。在他们实验的日子里,他们缺乏的一件事是结构化并发,在那个状态下有大量的网络材料写下了他们(比如你2017年的link 1)。一些当时有效的先入为主的观念在人们成熟后仍然存在,甚至在最近的帖子中得到了延续。

实际情况非常清楚--您所要理解的就是协程层次结构,它是通过Job对象进行中介的。无论它是launchasync还是任何其他协程构建器--它们的行为都是统一的。

记住这一点,让我们来看看您的示例:

runBlocking {
  async { throw Exception("Oops") }
}
通过编写async,您隐含地使用了this.async,其中this是已建立的CoroutineScope。它包含与runBlocking协程关联的Job实例。因此,async协程成为runBlocking的子级,因此当async协程失败时,后者会抛出异常。

runBlocking {
  async(SupervisorJob()) { throw Exception("Oops") }
}
在这里,您提供了一个没有父作业的独立作业实例。这打破了协程层次结构,runBlocking不会失败。事实上,runBlocking甚至不需要等待您的协同程序完成-添加一个delay(1000)来验证这一点。

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

这里没有新的推理--JobSupervisorJob,这无关紧要。您打破了协程层次结构,故障不会传播。

现在让我们探索更多的变体:

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,但随后发生了一些意想不到的事情:没有完成,程序永远挂起。为什么?

这可能是该机制中最棘手的部分,但一旦您仔细考虑,它仍然是完全有意义的。当您创建协程时,它会在内部创建自己的Job实例--无论您是否将作业作为参数显式提供给async,这种情况总是会发生。如果您提供显式作业,它将成为该内部创建的作业的父级

现在,在第一种情况下,您没有提供显式作业,父作业是由内部创建的作业。当runBlocking协同例程完成时,它会自动完成。但完成不会像取消那样传播到父级-您不会希望因为一个子协程正常完成而停止所有操作。

因此,当您创建自己的Job实例并将其作为async协程的父级提供时,您的工作不会通过任何方式完成。如果协程失败,则失败会传播到您的作业,但如果它正常完成,您的作业将永远保持正在进行中的原始状态。

最后,让我们再次引入SupervisorJob

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

这将永远运行,不会有任何输出,因为SupervisorJob会接受该异常。

这篇关于异常传播如何在CoroutineScope e.async上工作?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆