要让 Kotlin Channel 的 send 操作不挂起,核心思路是避免缓冲区满或未及时消费导致的挂起条件。以下是具体实现方法及策略:
一、配置缓冲区容量
-
设置
capacity参数- 默认无缓冲(
RENDEZVOUS)要求生产与消费协程同步,容易触发挂起16。 - 增大缓冲区容量:通过
Channel(capacity)指定足够大的缓冲区,例如Channel<Int>(100),允许短时间内积累未消费数据67。 - 无界缓冲区:使用
Channel.UNLIMITED,此时send永不挂起(需注意内存溢出风险)47。
- 默认无缓冲(
-
示例代码
val channel = Channel<Int>(capacity = Channel.UNLIMITED) // 无界缓冲区 launch { repeat(1000) { channel.send(it) // 不会挂起 } }
fun main():Unit = runBlocking {
val channel = Channel<Int>(Channel.UNLIMITED)
launch {
(1..1000) .forEach{
delay(2000)
channel.send(it)
println("生产一个:$it")
}
}
//第一种消费方式
launch {
// (1..1000) .forEach{
// delay(2000)
// var it = channel.receive()
// println("消费一个:$it")
//
// }
//第二种消费方式
val it = channel.iterator()
while (it.hasNext()){
delay(2000)
var it = channel.receive()
println("消费一个:$it")
}
}
//channel 是一个队列,队列是有缓冲区的,设置为 2147483647
//缓冲区不可能满,receive 并没有消费掉,此时send 也不会挂起,因为会存入缓冲区
二、调整溢出策略
通过 onBufferOverflow 参数指定缓冲区满时的行为(需配合 capacity 使用)4:
- **
DROP_OLDEST**:丢弃最旧元素,腾出空间接收新元素。 - **
DROP_LATEST**:丢弃当前发送的新元素,保留已有数据。 - **
CONFLATED**(特殊类型):仅保留最新元素,自动覆盖旧值4。
val channel = Channel<Int>(
capacity = 10,
onBufferOverflow = BufferOverflow.DROP_OLDEST // 缓冲区满时丢弃旧元素
)
三、使用非阻塞替代方法
-
**
trySend方法**
非挂起函数trySend立即返回ChannelResult,可判断是否成功5:val result = channel.trySend(element) if (result.isSuccess) { // 发送成功 } else { // 处理失败逻辑(如丢弃或重试) } -
结合
offer方法(已弃用,推荐trySend)
旧版本中可用offer,但新版本建议迁移至trySend5。
四、选择适用场景的策略
| 策略 | 适用场景 | 注意事项 |
|---|---|---|
| 设置大容量/无界缓冲区 | 高吞吐场景,允许延迟消费 | 警惕内存溢出风险47 |
DROP_OLDEST 或 CONFLATED | 实时数据处理,可容忍丢失部分历史数据 | 需评估数据完整性需求4 |
trySend 非阻塞发送 | 要求绝对非阻塞的异步场景 | 需处理发送失败逻辑5 |
五、典型错误规避
- 无限循环未关闭:即使使用无界通道,生产者完成后需调用
close(),否则消费者可能永久挂起38。 - 数据丢失风险:
DROP_*策略会主动丢弃数据,需确保业务逻辑允许