Выполнение нескольких вызовов API сопрограммы и ожидание их всех

поэтому обычно, когда вам нужно делать разные вызовы API и ждать, вы делаете что-то вроде этого:

viewModelScope.launch {
    withContext(dispatcherProvider.heavyTasks) {
        val apiResponse1 = api.get1() //suspend function
        val apiResponse2 = api.get2() //suspend function

        if (apiResponse1.isSuccessful() && apiResponse2.isSuccessful() { .. }
    }
}

но что произойдет, если мне придется выполнять несколько одновременных одинаковых вызовов API с разными параметрами:

viewModelScope.launch {
    withContext(dispatcherProvider.heavyTasks) {
        val multipleIds = listOf(1, 2, 3, 4, 5, ..)
        val content = arrayListOf<CustomObj>()

        multipleIds.forEach { id ->
             val apiResponse1 = api.get1(id) //suspend function

             if (apiResponse1.isSuccessful()) {
                 content.find { it.id == id }.enable = true
             }
        }

        liveData.postValue(content)
    }
}

Проблема со вторым подходом заключается в том, что он просматривает все идентификаторы списка multipleIds и выполняет асинхронные вызовы, но content, вероятно, будет опубликован до этого. Как я могу дождаться завершения всех ответов для каждого цикла и только затем postValue контента для просмотра?


person MaartinAndroid    schedule 14.07.2020    source источник
comment
Возможно, использование async и ожидание темы помогут   -  person dariush f    schedule 14.07.2020


Ответы (3)


Предпочтительный способ обеспечить выполнение нескольких асинхронных задач — использовать coroutineScope. Он будет приостановлен до тех пор, пока все дочерние задания, например. все вызовы launch или async завершены.

viewModelScope.launch {
    withContext(dispatcherProvider.heavyTasks) {
        val multipleIds = listOf(1, 2, 3, 4, 5, ..)
        val content = arrayListOf<CustomObj>()
        
        coroutineScope {
            multipleIds.forEach { id ->
                launch { // this will allow us to run multiple tasks in parallel
                    val apiResponse = api.get(id)
                    if (apiResponse.isSuccessful()) {
                        content.find { it.id == id }.enable = true
                    }
                }
           }
        }  // coroutineScope block will wait here until all child tasks are completed
        
        liveData.postValue(content)
    }
}

Если вам не нравится этот довольно неявный подход, вы также можете использовать более функциональный подход, отображая свои идентификаторы в список Deferred с помощью async, а затем ожидая их всех. Это также позволит вам запускать все задачи параллельно, но в итоге вы получите список результатов в правильном порядке.

viewModelScope.launch {
    withContext(dispatcherProvider.heavyTasks) {
        val multipleIds = listOf(1, 2, 3, 4, 5, ..)
        val content = arrayListOf<CustomObj>()

        val runningTasks = multipleIds.map { id ->
                async { // this will allow us to run multiple tasks in parallel
                    val apiResponse = api.get(id)
                    id to apiResponse // associate id and response for later
                }
        }

        val responses = runningTasks.awaitAll()

        responses.forEach { (id, response) ->
            if (response.isSuccessful()) {
                content.find { it.id == id }.enable = true
            }
        }
      
        liveData.postValue(content)
    }
}
person Adrian K    schedule 17.07.2020

Вместо forEach используйте map и сделайте то же самое внутри блока {}. Сохраните результат карты в переменную и опубликуйте эту переменную.

person Webfreak    schedule 14.07.2020

Чтобы получить параллельное поведение, вам нужно запустить новую сопрограмму для каждого идентификатора. Вы можете переместить multipleIds и content за пределы блока withContext. Также вы можете опубликовать результат после блока withContext, поскольку withContext является приостанавливающей функцией, поэтому каждая сопрограмма, созданная внутри, должна завершиться перед публикацией результата.

viewModelScope.launch {
    val multipleIds = listOf(1, 2, 3, 4, 5, ..)
    val content = arrayListOf<CustomObj>()

    withContext(dispatcherProvider.heavyTasks) {
        multipleIds.forEach { id ->
            launch {
                val apiResponse = api.get(id) //suspend function
                if (apiResponse.isSuccessful()) {
                    content.find { it.id == id }?.enable = true
                }
            }
        }
    }

    liveData.value = content
}
person Glenn Sandoval    schedule 14.07.2020
comment
Когда я ставлю точку останова в liveData.value - она ​​всегда работает как положено, все элементы списка верны (api.get(id) - был вызван и получен ответ), но когда я запускаю код без точки останова, все элементы списка являются ложными (логи показывают, что вызов API был успешным), что означает, что ответ пришел позже. Это не работает. - person MaartinAndroid; 14.07.2020