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

时间:2019-11-29 02:08:28

标签: kotlin kotlin-coroutines

给出以下代码段:

摘要[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
   }
}

第一个起作用,第二个片段崩溃。一般的解释是,在第一种情况下,asyncSupervisorJob继承了scope,而在第二种情况下,则没有。

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

2 个答案:

答案 0 :(得分:2)

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

在第一个代码段中,async工作是您创建的SupervisorJob的孩子,当它的孩子失败时,该工作不会被取消。在这种情况下,launch工作没有孩子。它只是捕获异常并完成。

答案 1 :(得分:1)

在第一个代码段中,由于您显式覆盖了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协程失败时,父作业会通过先取消所有其他子项(在这种情况下为无),然后取消自身来对它做出反应。 deferred.await()语句在这里没有区别,这就是为什么我删除了它。 launch协程将自动等待所有子协程完成。