kotlin 使用coroutineScope()和启动子协程并在其上调用join有什么区别?

yftpprvb  于 2023-01-05  发布在  Kotlin
关注(0)|答案(1)|浏览(141)

我试图理解Kotlin中的coroutineScope()挂起函数,但我很难理解此函数的确切用途。
根据Kotlin朗的文件,
这个函数是为并行分解工作而设计的。当这个作用域中的任何一个子协程失败时,这个作用域也会失败,并且所有其他的子协程都会被取消(关于不同的行为,请参见supervisorScope)。这个函数在给定的块及其所有子协程完成后立即返回。
但是我觉得这个行为可以通过启动一个子协程并调用join来实现。
比如说

suspend fun other() {
    coroutineScope {
        launch { // some task }
        async { // some task }
    }
}

这可以写为(作用域是对父协程创建的作用域的引用)

suspend fun other(scope: CoroutineScope) {
    scope.launch {
        launch { // some task }
        async { // some task }
    }.join()
}
  • 这两种方法之间是否有任何区别,因为它们看起来会产生相同的结果,而且似乎以相同的方式工作?
  • 如果不是,coroutineScope仅仅是一种减少从父协程传递作用域并在子协程上调用join的样板代码的方法吗?
yjghlzjz

yjghlzjz1#

    • 顶级域名注册商**

在示例中使用CoroutineScope会添加样板代码,更容易混淆、出错,并且可能会以不同的方式处理错误和取消等情况。coroutineScope()在此类情况下通常是首选。

    • 完整答案**

这两种模式在概念上是不同的,并且在不同的情况下使用。协程都是关于顺序代码和结构化并发的。顺序意味着我们可以写一个传统的代码,在原地等待,它不使用回调等,同时我们不会得到性能打击。结构化并发意味着并发任务有它们的所有者,任务由更小的子任务组成,这些子任务对框架是显式的。
通过将以上两个任务混合在一起,我们得到了一个非常易于使用和防错的并发模型,在大多数情况下,我们不必启动后台作业,然后手动管理它们,监视错误,处理取消等。我们只需将其分为子任务,然后就地加入它们-仅此而已。
在Kotlin中,这是用挂起函数来表示的。挂起函数总是在某个上下文中执行。这个上下文被隐式地传递到任何地方,协程框架提供了一些实用程序来方便地使用这个上下文。最常见的模式之一是先fork然后join,这正是coroutineScope()所做的。它创建了一个启动子任务的范围,我们可以'在所有的子节点都成功之前不要离开这个作用域,我们不需要手动传递作用域,我们不需要加入,我们不需要将错误从子节点传递给他们的兄弟节点和父节点,我们不需要将取消传递给子节点--这都是自动的。
因此,suspend函数和coroutineScope()应该是使用协程编写并发代码的默认方式。这种方式易于编写,易于阅读,并且是防错的。我们不能轻易泄漏后台任务,因为coroutineScope()不会让我们去任何地方。我们不能错误地忽略来自后台任务的错误。等等。
当然,在某些情况下,我们不能使用这种模式。有时,我们实际上只想启动一个长时间运行的任务并立即返回。有时,我们不认为调用者是任务的所有者。例如,我们可以有某种类型的服务来管理它的任务,我们只调度这些任务,但服务本身拥有它们。对于这些情况,我们可以使用CoroutineScope
通过显式地使用作用域,我们可以在与当前上下文不同的上下文中或从协同程序世界之外启动任务。我们通常有更多的控制,但同时我们部分地选择退出我上面提到的代码正确性保证。例如,如果我们忘记调用join(),我们很容易泄漏后台任务或以意外的顺序执行操作。此外,在您的例子中,如果调用other()的协程被取消,所有启动的操作仍将在后台运行。因此,我们应该仅在需要时显式使用CoroutineScope

    • 常见模式**

综上所述,在使用协程时,我们通常使用以下模式之一:

  • suspend函数--它在调用者上下文中运行,等待所有的子任务,它不在后台启动任何东西。
  • 函数接收CoroutineScope作为参数或接收器-通常,这意味着即使在返回后,该函数仍希望对上下文执行某些操作(因为否则它可能只是一个挂起函数)。它要么启动一些后台任务,要么将上下文存储在某个地方以供以后使用。
  • 使用自己的CoroutineScope来启动任务的常规函数。通常,这是某种保持其自定义上下文的服务。

至少对我来说,处于suspend状态并接收CoroutineScope的函数是相当令人困惑的,我们不完全清楚期望从它那里得到什么。它是在调用者上下文中还是在提供的上下文中执行操作?它是等待完成还是只在后台调度操作并立即返回?也许它会同时执行这两个操作:首先同步执行一些初始处理(因此suspend),但还要在后台调度其他任务(因此scope: CoroutineScope)?我们不知道这一点,我们必须阅读文档或源代码才能理解其行为。您的第二个示例是简单的suspend函数的不必要复杂性。
为了进一步说明我的观点,请看下面这个例子:

data class User(
    val firstName: String,
    val lastName: String,
) {
    fun getFullName(user: User) = ...
}

这个例子并不完美,但最主要的一点是,如果我们已经对一个用户调用了这个函数,为什么还要把user传递给getFullName(),这让人很困惑,因为我们不知道它是否返回了传递的用户的全名。我们调用函数的用户还是某种混合体如果这是一个没有接收User的成员函数或者是一个接收User的静态效用函数,所有的事情都很清楚,但是一个成员函数接收到一个User就很容易混淆,这和你的第二个例子类似,我们隐式和显式地传递上下文,但是我们不知道使用了哪一个,以及如何使用。

相关问题