首页 > 解决方案 > 从 Android 中的挂起函数并行调用 Kotlin 协程

问题描述

我对协程比较陌生,所以我想知道如何解决我的本地小问题,而无需大量重组我的 Android 代码。

这是一个简单的设置。我的 ViewModelsuspend从存储库调用一个函数:

// ...ViewModel.kt

fun loadData() {
    viewModelScope.launch {
        val data = dataRepository.loadData()
    }
}

这非常方便,因为我已经viewModelScope为我准备了 Android 并且我从我的存储库中调用了一个挂起函数。我不在乎存储库如何加载数据,我只是暂停直到它返回给我。

我的数据存储库使用以下命令进行了多次调用Retrofit

//...DataRepository.kt

@MainThread
suspend fun loadData(): ... {
    // Retrofit switches the contexts for me, just
    // calling `suspend fun getItems()` here.
    val items = retrofitApi.getItems()
    val itemIDs = items.map { it.id }

    // Next, getting overall list of subItems for each item. Again, each call and context
    // switch for `suspend fun retrofitApi.getSubItems(itemID)` is handled by Retrofit.
    val subItems = itemIDs.fold(mutableListOf()) { result, itemID ->
        result.apply {
            addAll(retrofitApi.getSubItems(itemID)) // <- sequential :(
        }
    }

    return Pair(items, subItems)
}

正如你所看到的,因为loadData()是一个挂起函数,所有的调用都retrofitApi.getSubItem(itemID)将按顺序执行。

但是,我想并行执行它们,就像async() / await()在协程中一样。

我想保持ViewModel代码不变——它不应该关心数据是如何加载的,只是从自己的范围内启动一个挂起函数。我也不想将任何类型的范围或其他对象传递到我的存储库。

如何在挂起函数中执行此操作?范围是否以某种方式隐含存在?打电话是async()可能的/允许的/好的做法吗?

标签: androidkotlinkotlin-coroutines

解决方案


您可以为此使用async和。awaitAll您需要一个协程范围来启动新的协程,但您可以使用coroutineScope.

suspend fun loadData(): Pair = coroutineScope {
    val items = retrofitApi.getItems()
    val itemIDs = items.map { it.id }
    val subItems = itemIDs.map { itemID ->
            async { retrofitApi.getSubItems(itemID) }
        }.awaitAll()
        .flatten()

    return Pair(items, subItems)
}

您可以flatten在原始代码中使用它来简化它。(只是指出它与分解这些并行任务无关。)它看起来像这样:

val subItems = itemIDs.map { itemID ->
        retrofitApi.getSubItems(itemID)
    }.flatten()

推荐阅读