Приостановка функции сопрограммы до получения результата, а затем продолжение - PullRequest
1 голос
/ 24 февраля 2020

Я медленно изучаю kotlin-coroutines и хочу начать с простого случая, когда я перебираю список и каждый раз вызываю вызов API, жду ответа, делаю с ним и затем повторяю процесс каждую итерацию.

viewModelScope.launch {
     list.forEach { item ->
          Log.d("TESTING", "forEach iteration before calling test(): ${item.id}")

          test()

          Log.d("TESTING", "forEach iteration after calling test(): ${item.id}")
     }
}

suspend fun test(item.id: String) = coroutineScope {
    Log.d("TESTING", "suspend test before receiving response: ${item.id}")

    val apiRepsonse = async {
        repository.doingApiCall() {
            Log.d("TESTING", "response: ${item.id}")
        }
    }.await()

    Log.d("TESTING", "suspend test after receiving response: ${item.id}")
}

Итак, если мы представим, что list содержит: { 10, 15, 25, 35 } (идентификаторы)

Я бы хотел получить в logcat следующее:

Log.d("TESTING", "forEach iteration before calling test(): 10")
Log.d("TESTING", "suspend test before receiving response: 10")
Log.d("TESTING", "response: 10")
Log.d("TESTING", "suspend test after receiving response: 10")
Log.d("TESTING", "forEach iteration after calling test(): 10")
___________________
Log.d("TESTING", "forEach iteration before calling test(): 15")
Log.d("TESTING", "suspend test before receiving response: 15")
Log.d("TESTING", "response: 15")
Log.d("TESTING", "suspend test after receiving response: 15")
Log.d("TESTING", "forEach iteration after calling test(): 15")
...

Но , прямо сейчас я получаю что-то вроде этого:

Log.d("TESTING", "forEach iteration before calling test(): 10")
Log.d("TESTING", "suspend test before receiving response: 10")
Log.d("TESTING", "suspend test after receiving response: 10")
Log.d("TESTING", "forEach iteration after calling test(): 10")
Log.d("TESTING", "forEach iteration before calling test(): 15")
Log.d("TESTING", "suspend test before receiving response: 15")
Log.d("TESTING", "suspend test after receiving response: 15")
Log.d("TESTING", "forEach iteration after calling test(): 15")

Log.d("TESTING", "response: 10")
Log.d("TESTING", "response: 15")

...

По сути, для каждого l oop перемещается и не ждет асинхронности API c ответ. Я думал, что .await() предназначен для этого сценария, но я мог бы что-то упустить здесь, поскольку это явно не работает, как я себе это представлял.

Ответы [ 3 ]

2 голосов
/ 24 февраля 2020

Скорее всего, repository.doingApiCall() не является надлежащей функцией приостановки и запускает некоторое асинхронное действие без приостановки сопрограммы. Похоже, что он принимает лямбда в качестве обратного вызова, еще один признак того, что это не приостановка функции.

И если бы это была функция приостановки, вам не нужно было бы отправлять ее с async.

В сторону, используя async и немедленно вызывая await для нее, более запутанный и менее оптимизированный способ использования withContext(Dispatchers.Default), а также он молча проглатывает исключения.

2 голосов
/ 24 февраля 2020

Измените

val apiRepsonse = async {
        repository.doingApiCall() {
            Log.d("TESTING", "response: ${item.id}")
        }
    }.await()

на

val apiRepsonse = async {
        Log.d("TESTING", "Start work in another 'thread'.")
        repository.doingApiCall() {
            Log.d("TESTING", "Finish work in another 'thread'.")
        }
    }.await()
Log.d("TESTING", "response: ${apiRepsonse.id}")

Таким образом, фактически используется значение, ожидаемое вашим ожидающим.

Я думаю, это устранит вашу путаницу в LogCat.


Я пытался решить вашу проблему самостоятельно, я написал ее как тест, который можно запустить, чтобы понял:

class TestFoo {

    @Test
    fun foo() {
        val job = GlobalScope.launch {
            listOf("10", "15").forEach { item ->
                println("TESTING forEach iteration before calling test(): ${item}")

                test(item)

                println("TESTING forEach iteration after calling test(): ${item}")
            }
        }
        while(job.isActive) {
            // wait
        }
    }

    private suspend fun test(item: String) = coroutineScope {
        println("TESTING suspend test before receiving response: ${item}")

        val apiRepsonse = async {
            delay(1000)
            println("TESTING response: ${item}")
            "Hello"
        }.await()

        println("TESTING suspend test after receiving response: ${item}")
    }

}

А вот "LogCat":

enter image description here

Получаете ли вы разные журналы из-за некоторой многопоточности, которая происходит внутри repository.doingApiCall()?

0 голосов
/ 24 февраля 2020

Не проверял, это должно работать:

// same code here
viewModelScope.launch {
     list.forEach { item ->
          Log.d("TESTING", "forEach iteration before calling test(): ${item.id}")

          test()

          Log.d("TESTING", "forEach iteration after calling test(): ${item.id}")
     }
}

suspend fun test(item.id: String) = withContext(Dispatchers.IO) {
    Log.d("TESTING", "suspend test before receiving response: ${item.id}")

    repository.doingApiCall() {
        Log.d("TESTING", "response: ${item.id}")
    }

    Log.d("TESTING", "suspend test after receiving response: ${item.id}")
}

Две вещи изменились:

  • вместо запуска нового coroutineScope, используйте withContext(), чтобы он приостановил звонящего
  • внутри withContext(), вам не нужно использовать async.await
...