Сопоставление значений канала. Потребление: каждый ждет, а не потребляет один раз за раз. - PullRequest
3 голосов
/ 04 мая 2019

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

MainActivity.kt

fun loadData() {
    textView.text = "LOADING"
    launch {
        repository.loadData().consumeEach { loaded ->
            withContext(Dispatchers.Main) {
                logd("Presenting: ${loaded.size}, $loaded")
                textView.text = loaded.joinToString { "$it\n" }
            }
        }
    }

Repository.kt

suspend fun loadData(): ReceiveChannel<List<String>> {
    return coroutineScope {
        produce(capacity = 2) {
            launch {
                val localData = local.loadData()
                send(localData)
            }
            launch {
                val remoteData = remote.loadData()
                send(remoteData)
            }
        }
    }
}

Remote.kt

override val data: MutableList<String> = mutableListOf("R1", "R2", "R3", "R4", "R5")

override suspend fun loadData(): List<String> {
    logd("Loading remote started")
    val wait = Random.nextLong(0, 500)
    delay(wait)
    logd("Remote loading took $wait")
    logd("Loading remote finished: ${data.size}, $data")
    return data
}

Local.kt

override val data: MutableList<String> = mutableListOf("L1", "L2", "L3", "L4", "L5")

override suspend fun loadData(): List<String> {
    logd("Loading local started")
    val wait = Random.nextLong(1000, 2000)
    delay(wait)
    logd("Local loading took $wait")
    logd("Loading local finished: ${data.size}, $data")
    return data
}

Я получаю это внутри консоли

D/Local: Loading local started
D/Remote: Loading remote started
D/Remote: Remote loading took 265
D/Remote: Loading remote finished: 5, [R1, R2, R3, R4, R5]
D/Local: Local loading took 1650
D/Local: Loading local finished: 5, [L1, L2, L3, L4, L5]
D/DispatchedCoroutine: Presenting: 5, [R1, R2, R3, R4, R5]
D/DispatchedCoroutine: Presenting: 5, [L1, L2, L3, L4, L5]

Похоже, что данные из обоих источников испускаются после достижения емкости,Я ожидаю, что потребитель сможет получать данные сразу после их отправки.Таким образом, вывод консоли выглядит примерно так:

D/Local: Loading local started
D/Remote: Loading remote started
D/Remote: Remote loading took 265
D/DispatchedCoroutine: Presenting: 5, [R1, R2, R3, R4, R5]
D/Remote: Loading remote finished: 5, [R1, R2, R3, R4, R5]
D/Local: Local loading took 1650
D/Local: Loading local finished: 5, [L1, L2, L3, L4, L5]
D/DispatchedCoroutine: Presenting: 5, [L1, L2, L3, L4, L5]

Как мне этого добиться (используя значения сразу после их отправки) с помощью coroutine.Channel?


EDIT # 1:

После удаления coroutineScope{...} из Repository#loadData() он начал работать как положено.Но теперь у меня есть проблема, что я должен передать область видимости в качестве параметра функции, который выглядит мне безобразно.

Repository.kt

suspend fun loadData(scope: CoroutineScope): ReceiveChannel<List<String>> {
    return scope.produce(capacity = 2) {
        launch {
            val localData = local.loadData()
            send(localData)
        }
        launch {
            val remoteData = remote.loadData()
            send(remoteData)
        }
        invokeOnClose {
            logd("Closing channel")
        }
    }
}

1 Ответ

1 голос
/ 04 мая 2019

Я думаю, ваш код выглядит хорошо, если вы делаете то, что ожидаете. Я думаю, что проблема у вас заключается в том, что регистрация не поступает в вашу консоль в тот момент, когда это происходит. Помните, что у самой регистрации есть своя собственная буферизация и потоки ввода-вывода для прохождения. Я попробовал ваш код и использовал println вместо этого, и я получаю ваше ожидаемое поведение. Чтобы подтвердить, вы можете вместо случайного ожидания увеличить время ожидания до 10 секунд для каждого и действительно заставить их произойти 1 за другим. Просто чтобы помочь вам подтвердить это для себя, вот моя не Android версия того, что вы пытаетесь сделать:

    fun main() = runBlocking {
    val start = System.currentTimeMillis()
    launch(Dispatchers.Unconfined) {
        loadData().consumeEach { loaded ->
            println("Presenting: ${loaded.size}, $loaded")
        }
    }.join()
    println("The whole thing took ${System.currentTimeMillis() - start}")
}

suspend fun CoroutineScope.loadData() = produce {
    launch {
        val localData = localloadData()
        send(localData)
    }
    launch {
        val remoteData = remoteloadData()
        send(remoteData)
    }
}

val remoteData: MutableList<String> = mutableListOf("R1", "R2", "R3", "R4", "R5")

suspend fun remoteloadData(): List<String> {
    println("Loading remote started")
    val wait = 500L
    delay(wait)
    println("Remote loading took $wait")
    println("Loading remote finished: ${remoteData.size}, $remoteData")
    return remoteData
}

val localData: MutableList<String> = mutableListOf("L1", "L2", "L3", "L4", "L5")

suspend fun localloadData(): List<String> {
    println("Loading local started")
    val wait = 1000L
    delay(wait)
    println("Local loading took $wait")
    println("Loading local finished: ${localData.size}, $localData")
    return localData
}

И он производит это:

Loading local started
Loading remote started
Remote loading took 500
Loading remote finished: 5, [R1, R2, R3, R4, R5]
Presenting: 5, [R1, R2, R3, R4, R5]
Local loading took 1000
Loading local finished: 5, [L1, L2, L3, L4, L5]
Presenting: 5, [L1, L2, L3, L4, L5]
The whole thing took 1046

РЕДАКТИРОВАТЬ: я удалил withContext(Dispatchers.Main), который вы использовали для обновления вашего значения - это действительно не нужно. На этом этапе вы уже выполнили асинхронную работу. Скорее, вам нужно указать контекст в вашем верхнем launch, как это происходит сейчас.

Остальная часть работы, описанной ниже, должна наследовать этот контекст, если не указано иное. Не нужно продолжать передавать контекст в качестве аргумента.

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

...