同一协程队列操作:蓝牙异步接收自定义协议数据如何按序解码
你现有代码的核心问题是每次收到新数据都会启动独立协程,多个协程并行执行时,即使加了Mutex也只能保证解码逻辑互斥,无法保证协程执行顺序和数据到达顺序一致,会出现后到的数据包先被处理的情况。
推荐采用「Channel做缓冲 + 单工作协程串行消费」的方案,天然保证处理顺序,不需要额外加锁,逻辑更简单稳定。
最终修改代码
ViewModel 层修改
// ViewModel private var coroutineJob = Job() // 工作协程绑定Default调度器,所有解码逻辑在后台执行 private val workScope = CoroutineScope(Dispatchers.Default + coroutineJob) // 数据缓冲通道,容量可根据实际场景调整,蓝牙场景用UNLIMITED足够 private val dataChannel = Channel<ByteArray>(Channel.UNLIMITED) init { // 启动唯一消费者协程,按入队顺序处理所有数据 workScope.launch { for (bytes in dataChannel) { // 上一个数据包处理完成后才会取下一个,天然串行 Packet.processMessage(bytes.toUByteArray()).collect { decodeResult -> updateData(decodeResult) } } } } fun getData(bytes: ByteArray) { // 主线程收到数据直接入队,非阻塞不会卡UI dataChannel.trySend(bytes) } override fun onCleared() { super.onCleared() // 页面销毁时释放资源,避免内存泄漏 coroutineJob.cancel() dataChannel.close() }
Packet 单例修改
因为现在已经是串行调用解码逻辑,不需要额外加Mutex锁,直接删掉锁逻辑即可:
object Packet { suspend fun processMessage(buffer: UByteArray) = flow { decode(buffer).collect { emit(it) } } private fun decode(buffer: UByteArray) = channelFlow { // 原有解码逻辑无需修改 offer() } }
优化说明
- 所有解码逻辑都在后台线程执行,不会阻塞主线程
- 单消费者协程严格按照数据到达顺序处理,完全避免了并行解码的时序问题
- 通道的trySend是非阻塞操作,符合LiveData回调在主线程执行的场景要求
- 如果需要处理背压,可以调整Channel的容量和溢出策略,比如设置容量为64、溢出策略为
BufferOverflow.DROP_OLDEST
内容的提问来源于stack exchange,提问作者Pico
相关产品推荐
相关产品推荐

