使用Channel解决kotlin 协程间同步问题

前面《Kotlin协程解决安卓回调地狱》一文介绍了Kotlin的协程在安卓异步编程中的重要作用。实践中,当有两个协程需要同步,如一个协程等待另一个协程发送相关信号才继续执行,这时采用协程间的Channel通信将是一个很好的解决方案。

Channel 是协程间一种通信方式,相当于一个管道,一个协程向管道内发送数据,另一个协程则从管道另一端接收数据。对于发送端,如果管道满,则无法发送数据,发送端会被挂起直至管道有空间;对于接收端,管道内没有数据,则被挂起,直至管道内有数据就唤醒执行。因此,使用Channel是一个非常好的协程间同步方法。

    val channel = Channel<Int>()  
    var i = 0
    //生产者 发
    val producer = GlobalScope.launch {
        while (true) {
            delay(1000)
            channel.send(i++)     //如果管道数据没有被取走,则挂起
        }
    }
    //消费者 收
    val consumer = GlobalScope.launch {
        while (true) {
            val value = channel.receive()     //如果管道内无数据,则挂起
            println("received <<<<<<<<<<<<<<<<<< $value")
        }
    }

代码 val channel = Channel<Int>() 定义了一个可以发送接收整数的管道,管道缺省缓冲为0,对于send只有数据被receive走,才会返回(否则阻塞)。

管道的缓冲参数有4种:

  1. RENDEZVOUS :0缓冲,为缺省方式,如果没有接收,则发送者会挂起等待;
  2. UNLIMITED :无限缓冲, 发送者一发送即返回,不管数据有没有被接收;
  3. CONFLATED:保留最新,不管发送者发了多少个,接收者只能收到最后一个,也是一发送就返回了,不管数据有没有被接收;
  4. BUFFERED:使用数值确定缓冲区大小。
val rendezvousChannel = Channel<String>()
val bufferedChannel = Channel<String>(10)
val conflatedChannel = Channel<String>(CONFLATED)
val unlimitedChannel = Channel<String>(UNLIMITED)

具体实践中,需要解决一个问题。即,当发送者协程终止时,接收者协程还在傻等数据到来,这时必须明确告诉接收者不要再等了,可以终止了。

对于发送者,可以直接close该Channel

rendezvousChannel.close()

则接收方就会收到一个异常。因此,接收方只要接到该异常就可以终止。

try {
    rendezvousChannel.receive()  //如果通道为空,则阻塞

}catch (e:Exception) {
    //do nothing
    //可以退出协程
}

发表评论