The use of Channel of Android Kotlin coroutine

Channel provides a convenient way to transfer a single value between multiple coroutines. Channels provide a way to transmit values ​​in a stream.

Simply put, Channel is used for communication between multiple coroutines, making the collaboration between coroutines smoother.

main content:

1. Basic use of channels

A Channel is very similar to a BlockingQueue, with one difference that it replaces a blocking put(…) operation and provides a pending send(…) and also replaces a blocking take(…) operation and provides a pending receive(…).

Simply put, it is to create a Channel , send to send data , and receive to receive data .

//创建Channel
val channel = Channel<Int>()
launch {
    // send发送数据
    for (x in 1..5) channel.send(x * x)
}
// receive接收数据
repeat(5) { println(channel.receive()) }
println("Done!")

print result

1
4
9
16
25
Done!

2. Channel buffer (capacity)

The channels shown in the above examples are all unbuffered. If send is called first, it will be suspended until receive is called, and if receive is called first, it will be suspended until send is called.

The Channel() factory function and produce builder pass an optional parameter capacity to specify the buffer size. Buffering allows a sender to send multiple elements before being suspended.

If the buffer is full and receive has not been called, send will hang.


val channel = Channel<Int>(4) // 启动带缓冲的通道
val sender = launch { // 启动发送者协程
    repeat(10) {
        println("Sending $it") 
        channel.send(it) 
    }
}
// 没有接收到东西……只是等待……
delay(1000)
sender.cancel() // 取消发送者协程

print result

Sending 0
Sending 1
Sending 2
Sending 3
Sending 4

By printing the results, we see that the first four elements were added to the buffer and the sender hangs while trying to send the fifth element.

Other fixed types of capacity

  • RENDEZVOUS, the default type; after sending data, if there is no receive, it will hang.
  • CONFLATED, the new element will replace the old element, receive will always get the latest data
  • UNLIMITED, there is no limit to the buffer; data can always be sent
  • BUFFERED, specifies the element size, the default is 64, and Send will be suspended when it is full (can be modified by program parameters).

3. Iterate Channel

Channel is similar to sequence, we can directly get Channel iterator (iterator) when reading

Through iterator (iterator) or for loop, you can receive multiple elements generated by Channel.

 /**
     * 不限制容量
     *
     * 通过迭代器遍历,获取数据
     */
    fun simpleChannel3() {
        val channel = Channel<Int>(Channel.UNLIMITED)
        launch {
            // 这里可能是消耗大量 CPU 运算的异步逻辑,我们将仅仅做 5 次整数的平方并发送
            for (x in 1..5) channel.send(x * x)
        }
        // 这里我们打印了 5 次被接收的整数:

        launch {
            val iterator = channel.iterator()
            while (iterator.hasNext()) {
                Log.d("liu", "Channel send :${iterator.next()}")
                delay(1000)
            }

//            //这种方式也可以
//            for (data in channel) {
//                Log.d("liu", "Channel send :${data}")
//                delay(1000)
//            }
        }

        Log.d("liu", "Done!")
    }

Four, produce and actor

In all the previous examples, we created Channels. Then, operate through different coroutines. So, is there a convenient way to build producers and consumers?

insert image description here

We can start a producer coroutine through the produce method and return a ReceiceChannel, and other coroutines can use this Channel to receive data. Conversely, we can use actor to start a consumer coroutine, return a SendChannel, and other coroutines send data through this Channel.

However, in the version used by the author, these two APIs are marked and have not yet been converted to full status.

  • produce
fun simpleChannelProduct() {
		//scope.produce。这里直接委托使用了MainScope
		//@ExperimentalCoroutinesApi  带标记的
        val receiveChannel = produce<Int> {
            var i = 0
            repeat(10) {
                delay(100)
                send(i++)
            }
        }
        launch {
            for (data in receiveChannel) {
                Log.d("liu", "Channel receive :${data}")
            }
        }
    }

Using a handy coroutine builder called produce, it's easy to get things right on the producer side, and we use the extension function consumeEach to replace the for loop on the consumer side:

 fun simpleChannelProduct2() {
        val receiveChannel = produce<Int> {
            var i = 0
            repeat(10) {
                delay(100)
                send(i++)
            }
        }
        launch {
            receiveChannel.consumeEach {value ->
                Log.d("liu", "Channel receive :${value}")
            }
        }
    }

  • actor
fun simpleChannelSend() {
		//@ObsoleteCoroutinesApi  带标记的
        val sendChannel = actor<Int> {
            while (true) {
                val data = receive()
                Log.d("liu", "Channel receive :${data}")
            }
        }
        launch {
            for (i in 0..3) {
                delay(100)
                sendChannel.send(i++)
            }
        }
    }

5. Closing of Channel Channel

The Channel returned by produce and actor will be closed when the corresponding coroutine is executed. Therefore, Channel is also called hot data flow

For Channel, if we call its Close method, it will stop accepting new elements immediately , that is, its isClosedForSend will return true immediately at this time, and due to the existence of the Channel buffer, some data may not be processed at this time, so the Channel will not be closed until all elements are read and isColsedForReceive returns true.

We send 3 in an instant, and close immediately after sending. When accepting, accept one every 1 second. In this way, when it is closed, there is data in the Channel, let's look at the status of the Channel

sample code,

 /**
     * Channel的关闭
     */
    fun simpleChannelClose() {
        val channel = Channel<Int>(3)
        launch {
            List(3) {
                channel.send(it)
                Log.d("liu", "Channel send :${it}")
            }
            channel.close()

            Log.d("liu", "Channel isClosedForSend :${channel.isClosedForSend}")
            Log.d("liu", "Channel isClosedForReceive :${channel.isClosedForReceive}")
            Log.d("liu", "Channel isEmpty :${channel.isEmpty}")
        }

        launch {
            for (data in channel) {
                Log.d("liu", "Channel receive :${data}")
                delay(1000)
            }

            Log.d("liu", "Channel isClosedForSend :${channel.isClosedForSend}")
            Log.d("liu", "Channel isClosedForReceive :${channel.isClosedForReceive}")
            Log.d("liu", "Channel isEmpty :${channel.isEmpty}")
        }
    }

六、BroadcastChannel

As mentioned earlier, there is a one-to-many situation between the sending end and the receiving end in the Channel. From the perspective of data processing itself, although there are multiple receiving ends, the same element will only be received by one receiving end. Broadcasting is not the case, multiple receivers can receive it, and there is no mutual exclusion.


  /**
     * BroadcastChannel
     */

    fun simpleBroadcastChannel() {
        val broadcastChannel = BroadcastChannel<Int>(Channel.BUFFERED)
        //2种创建方式
//        val channel = Channel<Int>()
//        val broadcastChannel = channel.broadcast(Channel.BUFFERED)


        launch {
            List(3) {
                delay(1000)
                broadcastChannel.send(it)
            }
        }

        List(3) {
            launch {
                val receiveChannel = broadcastChannel.openSubscription()
                for (i in receiveChannel) {
                    Log.d("liu", "$it : Channel is :${i}")
                }
            }
        }
    }


Seven, multiplexing (experimental nature)

In a data communication system or a computer network system, the bandwidth or capacity of the transmission medium is often greater than the demand for transmitting a single signal. In order to effectively use the communication line, it is hoped that one channel can transmit multiple signals at the same time. This is the so-called multiplexing technology.

Chinese document: click

await multiplexing

The two APIs obtain data from the network and the local cache respectively, and whichever is expected to be returned first will be used first.



    suspend fun CoroutineScope.obtainDataFromNetwork() = async(Dispatchers.IO) {
        delay(500)
        "I'm From Network"
    }

    fun CoroutineScope.obtainDataFromCache() = async(Dispatchers.IO) {
        delay(300)
        "I'm From Cache"
    }

    fun awaitTest() {
        launch {
            val data = obtainDataFromNetwork()
            val cacheData = obtainDataFromCache()

            /**
             * 拿到最快的返回结果,哪个返回快用哪个
             */
            val dataResult = select<Test> {
                cacheData.onAwait { Test(it, true) }
                data.onAwait { Test(it, false) }
            }

            dataResult.data?.let {
                Log.d("liu", "$it ")
            }
        }
    }


Channel multiplexing


fun channelTest() {
        val channels = listOf(Channel<Int>(), Channel<Int>())

        launch {
            delay(100)
            channels[0].send(0)
        }
        launch {
            delay(200)
            channels[1].send(1)
        }

        launch {
            val result = select<Int> {
                channels.forEach { channel ->
                    channel.onReceive { it }
                }
                Log.d("liu", "$result ")
            }
        }
    }


SelectClause

What events can be selected? In fact, all types of events that can be selected SelectClause N:

  • SelectClause0: The corresponding event has no return value, such as join, then onJoin is the event SelectClauseN type. When used, onJoin is a parameterless function
  • SelectClause1: The corresponding event has a return value, the previous onAwait, onReceive all belong to this
  • SelectClause2: The corresponding event has a return value, and an additional parameter is required. For example, Channel.onSend has 2 parameters, 1, which is the value of the data type of the Channel, indicating the value to be sent; 2, the callback parameter when the send is successful.

If we want to confirm whether the suspending function supports the select function, we only need to check whether there is a corresponding SelectClauseN type callback.

Guess you like

Origin blog.csdn.net/ecliujianbo/article/details/128449471
Recommended