如果直接在协程块内调用,为什么 `async` 不继承 SupervisorJob?

Why `async` doesn't inherit SupervisorJob if called directly inside the coroutine block?

给定以下代码片段:

代码段 [1]

val job = SupervisorJob()
val scope = CoroutineScope(Dispatchers.IO + job)
scope.launch {
   try {
     scope.async {
       throw RuntimeException("Oops!")
     }.await()
   } catch(e: Exception) {
    // Handle exception
   }
}

和代码段 [2]

val job = SupervisorJob()
val scope = CoroutineScope(Dispatchers.IO + job)
scope.launch {
   try {
     async {
       throw RuntimeException("Oops!")
     }.await()
   } catch(e: Exception) {
    // Handle exception
   }
}

第一个有效,第二个片段崩溃。一般的解释是,在第一种情况下,async继承了scopeSupervisorJob,而在第二种情况下则没有。

我的问题是,如果 asyncCoroutineScope 的扩展函数,为什么在第二种情况下(崩溃)它不以相同的方式继承 SupervisorJob

launch 创建一个新作业,并将其传播到块接收的 CoroutineScope 中。在第二个片段中,async 作业是 launch 作业的子作业,并且 launch 作业在其子作业失败时被取消。

在第一个片段中,async 作业是您创建的 SupervisorJob 的子作业,当其子作业失败时不会被取消。本例中的 launch 作业没有子作业。它只是捕获异常并完成。

在您的第一个代码段中,因为您明确覆盖了 launch 构建器建立的范围,所以它与您在其中的 async 块之间没有父子关系。

这是编写第一个片段的等效方法:

val deferred = scope.async {
    throw RuntimeException()
}

scope.launch {
    try {
        deferred.await()
    } catch(e: Exception) {
    }
}

async 协程因异常而失败。它的父级是 SupervisorJob,它忽略了失败。 launch 协程调用 Deferred.await(),这会引发异常。您捕获了异常并且没有任何失败。

您的第二个片段可以重写如下:

scope.launch {
    val innerScope = this

    innerScope.async {
        throw RuntimeException()
    }
}

在这里您可以明确地看到 async 块继承了哪个范围。它不是 SupervisorJob 的顶级,而是 launch 创建的内部。当 async 协程失败时,父作业对其作出反应,首先取消所有其他子程序(在本例中为 none),然后取消自身。 deferred.await() 语句在这里没有任何区别,这就是我删除它的原因。 launch 协程将自动等待所有子协程完成。