Kotlin协程中如何优雅等待传递断开信号的Channel关闭
我有一个仅用于传递连接已关闭信号的Channel,我认为采用Unit类型Channel、在连接断开时关闭该Channel是最合适的实现方案,简化示例代码如下:
class Connection { val disconnect = Channel<Unit>(1, BufferOverflow.DROP_LATEST) fun connect() { backingConnection.disconnectCallback = { disconnect.close() } backingConnection.connect() } }
目前我遇到的问题是如何等待该Channel关闭,我尝试了两种实现方案,但二者都略显冗余:
lifecycleScope.launch { try { connection.disconnect.receive() } catch (ignore: Exception) {} finish() }
或者
lifecycleScope.launch { @Suppress("ControlFlowWithEmptyBody") for (dummy in connection.disconnect) {} finish() }
请问是否存在更简洁优雅的实现方案?
方案1:替换原语,用Job替代Channel(最推荐)
你这个场景本质是需要一个一次性可等待的生命周期信号,不需要传递任何实际数据,Job才是kotlin协程中最匹配这个语义的原语,比Channel轻量很多,也不需要配置缓冲区、溢出策略等无关参数。
改造后的Connection代码如下:
class Connection { // 对外仅暴露只读Job接口,避免外部随意触发完成 val disconnect: Job = Job() fun connect() { backingConnection.disconnectCallback = { disconnect.complete() } backingConnection.connect() } }
等待断开信号时直接调用join()即可,代码简洁语义明确,不需要捕获异常、不需要加注解抑制警告:
lifecycleScope.launch { connection.disconnect.join() finish() }
这个实现和你原有Channel的行为完全一致:如果断开信号已经触发,join()会直接返回不会挂起;如果还未触发,会挂起直到信号到来。另外你原有try-catch的写法存在隐藏问题,会吞掉协程取消时抛出的CancellationException,破坏结构化并发逻辑,该实现不存在这个问题,会正常响应协程取消。
方案2:保留Channel实现的简化写法
如果你因为其他原因必须保留Channel的实现,不需要写try-catch或者空循环,直接调用receiveCatching()即可:
lifecycleScope.launch { connection.disconnect.receiveCatching() finish() }
receiveCatching()不会在Channel正常关闭时抛出异常,无论Channel是收到了元素还是被关闭,调用后都会正常恢复执行后续逻辑,同时它会正常抛出协程取消对应的CancellationException,不会破坏结构化并发,比你原来的try-catch写法更安全。
内容的提问来源于stack exchange,提问作者AndreKR

