面试题答案
一键面试挂起函数异常对通道操作的影响
在Kotlin协程中,当一个挂起函数执行过程中抛出异常时,与之相关的通道操作会受到如下影响:
- 发送端异常:如果在向通道发送数据的挂起函数(如
send
)中抛出异常,通道的isClosedForSend
属性会变为true
,意味着不能再向通道发送数据。如果接收端正在等待接收数据,它可能会收到ClosedSendChannelException
异常(如果通道为空且关闭)。 - 接收端异常:如果在从通道接收数据的挂起函数(如
receive
)中抛出异常,通道的isClosedForReceive
属性会变为true
,不能再从通道接收数据。发送端如果尝试发送数据,可能会收到ClosedReceiveChannelException
异常(如果通道已满且接收端关闭)。
示例代码:
import kotlinx.coroutines.*
import kotlinx.coroutines.channels.Channel
fun main() = runBlocking<Unit> {
val channel = Channel<Int>()
launch {
try {
channel.send(1)
// 模拟异常
throw RuntimeException("发送时出错")
channel.send(2)
} catch (e: Exception) {
println("发送端捕获异常: $e")
} finally {
channel.close()
}
}
launch {
try {
val value = channel.receive()
println("接收到: $value")
val anotherValue = channel.receive()
} catch (e: Exception) {
println("接收端捕获异常: $e")
}
}
}
处理通道背压问题
当通道数据发送速度大于接收速度时,会产生背压问题。可以通过以下几种方式处理:
- 缓冲通道:创建通道时指定缓冲区大小,这样发送端可以在缓冲区满之前持续发送数据,而不会立即挂起。
import kotlinx.coroutines.*
import kotlinx.coroutines.channels.Channel
fun main() = runBlocking<Unit> {
val channel = Channel<Int>(10)
launch {
for (i in 1..20) {
channel.send(i)
println("发送: $i")
}
channel.close()
}
launch {
for (value in channel) {
delay(200)
println("接收: $value")
}
}
}
- 使用
produce
和receiveOrNull
:produce
函数返回一个ReceiveChannel
,通过receiveOrNull
可以在通道关闭时优雅地结束接收循环。
import kotlinx.coroutines.*
import kotlinx.coroutines.channels.ReceiveChannel
import kotlinx.coroutines.channels.produce
fun produceNumbers(): ReceiveChannel<Int> = coroutineScope {
produce {
for (i in 1..20) {
send(i)
println("发送: $i")
}
}
}
fun main() = runBlocking<Unit> {
val channel = produceNumbers()
launch {
var value: Int?
while ((value = channel.receiveOrNull()) != null) {
delay(200)
println("接收: $value")
}
}
delay(5000)
}
- 使用
flow
和collect
:Flow
提供了更高级的背压处理策略,如buffer
、collectLatest
等。
import kotlinx.coroutines.*
import kotlinx.coroutines.flow.*
fun main() = runBlocking<Unit> {
flow {
for (i in 1..20) {
emit(i)
println("发送: $i")
}
}.buffer()
.collect { value ->
delay(200)
println("接收: $value")
}
}