Выполнение нескольких вызовов API сопрограмм и ожидание их всех - PullRequest
0 голосов
/ 14 июля 2020

поэтому обычно, когда вам нужно выполнить разные вызовы 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)
    }
}

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

Ответы [ 3 ]

4 голосов
/ 17 июля 2020

Предпочтительный способ обеспечить выполнение пары асинхронных задач - использовать 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)
    }
}
0 голосов
/ 14 июля 2020

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

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
}
0 голосов
/ 14 июля 2020

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...