Вызов Kotlin сопрограмм параллельно с функцией приостановки в Android - PullRequest
0 голосов
/ 24 марта 2020

Я относительно новичок в сопрограммах, поэтому мне было интересно, как я могу решить свою небольшую локальную проблему без значительной перестройки моих Android кодов.

Вот простая настройка. Моя ViewModel вызывает функцию suspend из репозитория:

// ...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()? 1025 *

Ответы [ 2 ]

1 голос
/ 24 марта 2020

Да, чтобы выполнить несколько сопрограмм одновременно, вам нужно будет использовать async средство запуска несколько раз, а затем вызвать await для всех Deferred, полученных из async вызовов.

Вы можете найти очень похожий пример здесь .

Это самая важная часть кода:

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
}
1 голос
/ 24 марта 2020

Для этого вы можете использовать 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()
...