协程通道值消耗每次等待而不是一次消耗

时间:2019-05-04 14:08:46

标签: android kotlin kotlin-coroutines

我在弄清楚如何使用通道时遇到了问题,我希望它们在调用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实现此目标(在发送值后立即使用它们)?


编辑#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 个答案:

答案 0 :(得分:1)

我认为您的代码可以按您期望的方式执行。我认为您遇到的问题是,日志记录在发生时尚未到达控制台。请记住,日志记录本身具有其自己的缓冲和IO线程来传递。我已经尝试了您的代码,并改用Column C 1 1 0 ,但我得到了预期的行为。要确认,您可以不进行随机等待,而将每个等待的等待时间增加到10秒,并真正使它们接连发生。为了帮助您自己确认这一点,这是您尝试做的事情的我的非Android版本:

println

它产生了这个:

    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)中指定上下文。

除非另有说明,否则下面的其余工作应继承该上下文。无需继续传递上下文作为参数。

如果您确实处在另一个上下文取代继承的上下文的位置,则可能将其作为参数传递可能是这样做的一种方式,但我的首选是找到解决方法并以某种方式表达它确实是从调用上下文继承的。