Kotlin 1.5 Channel中trySendBlocking()与trySend()的区别及使用疑问
Kotlin 1.5 Channel 发送方法差异解答
核心前提
首先明确三个发送方法的本质区别:
trySend():立即尝试向Channel写入数据,不管Channel是否已满,立刻返回结果,不会阻塞也不会挂起send():协程挂起函数,Channel满时会挂起当前协程等待空位,不阻塞线程trySendBlocking():阻塞API,Channel满时会阻塞当前调用线程等待空位,内部确实通过runBlocking实现
针对三个疑问的解答
1. 为什么不建议用自旋trySend替代trySendBlocking
你写的do-while自旋重试方案属于忙等待,存在非常明显的缺陷:
- 只要Channel满的时间超过几毫秒,就会空占CPU资源,极端情况下如果当前线程优先级高于消费者线程,还会出现「生产者占满CPU自旋,消费者拿不到CPU消费Channel,导致Channel一直满,自旋永远不结束」的死锁场景
- 你假设的「容量足够、消费速度足够快」属于理想场景,生产环境可能出现GC停顿、消费者逻辑突发阻塞、流量尖刺等异常情况,无法100%保证Channel永远不满
如果你的Channel确实是UNLIMITED无限容量,trySend永远不会失败,那你完全可以直接用trySend,不需要任何阻塞或重试逻辑。
2. trySendBlocking使用runBlocking是否符合规范
官方对runBlocking的使用限制是不要在协程内部用它桥接阻塞代码和挂起代码,避免阻塞协程调度线程、破坏协程的非阻塞特性。
但trySendBlocking的设计目标就是给非协程的阻塞上下文使用的:比如你的调用逻辑跑在普通Java线程池、IO阻塞线程里,本身就允许阻塞当前线程,这时候用trySendBlocking阻塞等待Channel空位完全符合设计预期,不存在问题。如果你是在协程内部调用,根本不需要用这个方法,直接用挂起函数send()即可。
3. trySend是否线程安全
Channel的所有公开操作(包括trySend、tryReceive、send、receive)全都是线程安全的,多线程并发调用同一个Channel的trySend不会出现任何并发问题,这不是优先选择trySendBlocking的原因。
优先选择trySendBlocking的唯一原因是:你在非协程的阻塞上下文里,需要保证消息必须发送成功,同时不想用自旋浪费CPU资源。
方案选型建议
- 确定Channel永远不会满:直接用
trySend(command).getOrThrow()即可 - 调用方在协程内:使用挂起函数
send(),性能最优,不会阻塞线程 - 调用方在非协程阻塞上下文,需要保证消息必达:使用
trySendBlocking(),不要自行实现自旋重试
内容的提问来源于stack exchange,提问作者gpo
相关产品推荐
相关产品推荐

