我对协程还比较陌生,所以我想知道如何在不大量重构Android代码的情况下解决本地的小问题。
这是一个简单的设置。我的ViewModel从存储库中调用suspend
函数:
// ...ViewModel.kt
fun loadData() {
viewModelScope.launch {
val data = dataRepository.loadData()
}
}
这非常方便,因为我有一个viewModelScope
由Android为我准备,并且我从存储库中调用了suspend函数。我不在乎存储库如何加载数据,我只是暂停直到数据返回给我。
我的数据存储库使用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
代码不变-它不在乎数据是如何加载的,只是从自己的作用域中启动一个suspend函数。我也不想将任何范围或其他对象传递到我的存储库。
如何在暂停功能中执行此操作?范围在某种程度上隐式存在吗?打电话给async()
是可能的/允许的/好的做法吗?
答案 0 :(得分:1)
您可以为此使用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()
答案 1 :(得分:1)
是的,要同时执行多个协程,您将需要多次使用async
启动器,然后在await
调用返回的所有Deferred
上调用async
。 / p>
您可以找到一个非常相似的示例here。
这是代码中最相关的部分:
private suspend fun computePartialProducts(computationRanges: Array<ComputationRange>) : List<BigInteger> = coroutineScope {
return@coroutineScope withContext(Dispatchers.IO) {
return@withContext computationRanges.map {
computeProductForRangeAsync(it)
}.awaitAll()
}
}
private fun CoroutineScope.computeProductForRangeAsync(computationRange: ComputationRange) : Deferred<BigInteger> = async(Dispatchers.IO) {
val rangeStart = computationRange.start
val rangeEnd = computationRange.end
var product = BigInteger("1")
for (num in rangeStart..rangeEnd) {
if (!isActive) {
break
}
product = product.multiply(BigInteger(num.toString()))
}
return@async product
}