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



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

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

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

*** 或钩接到生命周期感知的 sourrouding 框架给出的某个范围

您可以使用任何您喜欢的范围。这不会影响您的问题。不鼓励使用 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()
}
}
}

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

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即可。

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

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

}

最新更新