【问题标题】:How to run several Kotlin coroutines in parallel and wait for them to complete before proceeding如何并行运行多个 Kotlin 协程并等待它们完成后再继续
【发布时间】:2020-04-17 01:44:37
【问题描述】:

我需要并行运行 2 个协程并等待它们完成后再继续。下面的代码有效,但它使用GlobalScope,这不是最好的方法。

有没有更好的办法?

fun getInfo(onSuccess: () -> Unit, onError: () -> Unit) {
        GlobalScope.launch(Dispatchers.IO) {
            try {
                coroutineScope {
                    launch { getOne() }
                    launch { getTwo() }
                }
                onSuccess.invoke()
            } catch (e: Throwable) {
                onError.invoke()
            }
        }
    }

【问题讨论】:

    标签: kotlin kotlin-coroutines


    【解决方案1】:

    我建议将getInfo 实现为一个挂起函数,该函数知道它应该运行的上下文。这样,您从哪个上下文调用它都无关紧要 (*)。

    此外,之后我不会使用回调来继续。 您可以根据getInfo() 返回的内容来决定如何继续(**)。

    这实际上是协程最棒的地方,您可以将基于回调的代码转换为读起来像顺序代码的代码。

    由于您不关心getOne()getTwo() 的结果,因此使用launch 是正确的方法。它返回一个Job。您可以暂停协程,直到两个函数都使用 joinAll() 完成,这可以在 Collection<Job> 上调用。

    suspend fun getInfo() = withContext(Dispatchers.IO) {
        try {
            listOf(
                launch { getOne() },
                launch { getTwo() }
            ).joinAll()
            false
        } catch (e: Throwable) {
            true
        }
    }
    

    您不需要使用GlobalScope,只需创建您自己的 (***)。

    我使用Default 作为启动getInfo 的上下文,但任何其他上下文也可以,因为getInfo 将在它应该运行的那个上运行。

    val newScope = CoroutineScope(Dispatchers.Default).launch {
        val error = getInfo()
        if(error) {
            onSuccess()
        } else {
            onError()
        }
    }
    // "newScope" can be cancelled any time
    

    * 如果我用Dispatcher.IO 假装这两个函数正在做一些长时间运行的 IO 工作。

    ** 我在这里使用了一个简单的布尔值,但当然你可以返回更有意义的值。

    *** 或挂钩到由具有生命周期感知能力的复杂框架给出的某个范围

    【讨论】:

    • 谢谢,很好的回答,我喜欢你让它返回布尔值的想法。
    • 如果您创建自己的CoroutineScope,请务必在某个时候取消它。否则只需使用GlobalScope
    • @DominicFischer GlobalScope 也将存在,直到应用程序终止。因此,您需要注意将范围集成到应用程序的生命周期中,但这取决于应用程序的类型,我什至提到“或挂钩到由生命周期感知的完善框架提供的某个范围”
    【解决方案2】:

    您可以使用任何您喜欢的范围。这不影响你的问题。不鼓励使用 GlobalScope,因为它不封装您的任务。

    实际使用哪个范围来启动您的协程将完全取决于您正在执行的操作的上下文以及您使用的封装策略。

    要一次启动多个协程并等待所有协程,您可以使用asyncawait()。如果您只需要在继续之前完成所有这些操作,您可以在列表中使用awaitAll()

    您使用 IO 调度程序来运行您的回调似乎很奇怪,但我会保留它,因为我对您的代码上下文视而不见。

    fun getInfo(onSuccess: () -> Unit, onError: () -> Unit) {
            GlobalScope.launch(Dispatchers.IO) {
                try {
                    coroutineScope {
                        listOf(
                            async { getOne() }
                            async { getTwo() }
                        }.awaitAll()
                    }
                    onSuccess.invoke()
                } catch (e: Throwable) {
                    onError.invoke()
                }
            }
        }
    

    【讨论】:

      【解决方案3】:

      您可以在您正在处理的类中创建一个CoroutineScope,只需调用launch builder 即可构建协程。像这样的:

      class MyClass: CoroutineScope by CoroutineScope(Dispatchers.IO) { // or [by MainScope()]
      
          fun getInfo(onSuccess: () -> Unit, onError: () -> Unit) {
              launch {
                  try {
                      val jobA = launch { getOne() }
                      val jobB = launch { getTwo() }
                      joinAll(jobA, jobB)
                      onSuccess.invoke()
                  } catch (e: Throwable) {
                      onError.invoke()
                  }
              }
          }
      
          fun clear() { // Call this method properly
              this.cancel()
          }
      
      }
      

      确保通过正确调用cancel 来取消在作用域内运行的任何协程。

      或者,如果您在 ViewModel 中工作,只需使用 viewModelScope

      【讨论】:

      • 我不相信你的例子会起作用,因为父 CoroutineScope 实际上并不包含工作。除了Dispatchers.IO,您还需要手动添加它
      • @Kiskae 实际上它有效,我正在使用委托创建范围,正如官方文档所述 here
      • @GlennSandoval 为什么要围绕它包装一个类?只是为了获得自己的范围?我觉得有点过分了。
      • @WilliMentzel。我假设他在一个班级内工作并且他正在寻找GlobalScope.launch 的替代品,所以我将展示如何为该班级构建一个范围。这就是为什么我建议如果他在 ViewModel 中工作,那么他应该改用 viewModelScope
      • @GlennSandoval 您提出的建议是正确的,但对于这种情况来说太多了。它只是做了太多(不必要的)假设。我们不知道 getInfo 是否在一个类中,并且我们正在处理一个 android 应用程序,这不是问题的重点。不要走错路!
      【解决方案4】:

      实际上,根据公认的答案,还有更好的方法:

      suspend fun getInfo() = withContext(Dispatchers.IO) {
      try {
          coroutineScope {
              launch { getOne() }
              launch { getTwo() }
          }
          false
      } catch (e: Throwable) {
          true
      }
      

      }

      【讨论】:

        猜你喜欢
        • 2019-11-01
        • 2019-03-24
        • 2022-10-31
        • 2015-08-21
        • 1970-01-01
        • 2013-04-25
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多