如何并行运行多个Kotlin协程并等待它们完成后再继续

时间:2020-04-17 01:44:37

标签: kotlin kotlin-coroutines

我需要并行运行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()
            }
        }
    }

4 个答案:

答案 0 :(得分: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()
            }
        }
    }

答案 1 :(得分:2)

我建议将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工作。

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

***或进入由生命周期感知的源框架给出的范围

答案 2 :(得分:1)

您可以在正在处理的类中创建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

答案 3 :(得分:0)

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

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

}