Flow is called cold flow. Since there is cold flow, there is heat flow, and pipe channel is heat flow. Channel is a concurrent and secure queue, which is mainly used to handle the communication between processes
The sender and receiver are two different processes. The data sent and transmitted between them is through this pipeline, a send and a receive
val channel = Channel<String>{ } viewModelScope.launch { channel.send("123") } viewModelScope.launch { val receive = channel.receive() if(receive == "123"){ Log.e(TAG,"I got it") } }
Channel creates a channel to send a string. After one data is sent by coroutine 1, it receives this parameter in coroutine 2 and continues to perform subsequent operations
1 Channel capacity
Since the Channel is a queue, there will be capacity. By default, capacity = 1
public fun <E> Channel( capacity: Int = RENDEZVOUS, onBufferOverflow: BufferOverflow = BufferOverflow.SUSPEND, onUndeliveredElement: ((E) -> Unit)? = null ): Channel<E> = when (capacity) { RENDEZVOUS -> { if (onBufferOverflow == BufferOverflow.SUSPEND) RendezvousChannel(onUndeliveredElement) // an efficient implementation of rendezvous channel else ArrayChannel(1, onBufferOverflow, onUndeliveredElement) // support buffer overflow with buffered channel }
At the same time, there is also a buffer overflow to store the received data. When the buffer is full, send will hang until the buffer has new space; Similarly, if the cache is empty, the receive will hang
2 Channel iteration
The Channel has an iterator. By getting the iterator of the pipeline, you can get the collected data and replace the receive
viewModelScope.launch { (1..3).asFlow().collect { channel.send("123") } } viewModelScope.launch { val iterator = channel.iterator() while (iterator.hasNext()){ Log.e(TAG,"I got it ${iterator.next()}") } }
3. Channel builder
In the preface, there is a builder used, that is, Channel can directly build a pipeline. There are two ways to build producer or consumer collaboration pipelines
3.1 produce
produce is used to generate a ReceiveChannel, which can be used to receive data. It is equivalent to creating a producer. This is also an extension function of CorotinueScope, which is equivalent to creating a collaboration and a pipeline at the same time
val channel= viewModelScope.produce<String> { send("123") } viewModelScope.launch { val receive = channel.receive() if(receive == "123"){ Log.e(TAG,"I got it") } }
3.2 actor
In contrast to produce, the actor creates a SendChannel, which is used to send data and can receive data in the scope of the collaboration
val channel = viewModelScope.actor<String> { val receive = receive() if (receive == "123") { Log.e(TAG, "I got it") } } viewModelScope.launch { channel.send("123") }
4. Channel shutdown
Because the sending and receiving of data are executed in the process of cooperation, when the process of cooperation is completed, the Channel will be closed, which is natural
If you actively call the close method of the Channel, the Channel will immediately stop sending new elements, and the attribute isClosedForSend of the Channel will immediately return true
val channel = viewModelScope.actor<String>(Dispatchers.Main) { while (true){ val receive = receive() if (receive == "123") { Log.e(TAG, "I got it") } } } viewModelScope.launch { (1..5).asFlow().collect { delay(500) channel.send("123") } } delay(1000) Log.e(TAG, "Ready to close the pipe") channel.close()
When the channel is closed after 1s, it will immediately stop sending data, and the receiver throws an exception, ClosedReceiveChannelException, indicating that the current channel has been closed and cannot receive data
5 await multiplexing
Multiplexing is a concept in the network. The bandwidth or capacity of the transmission medium is often greater than the demand of a single transmission signal. We hope to transmit multiple signals in one channel at the same time
In the Channel, there is only one Channel, so I hope the receiver can receive multiple data sources, instead of sending a single String data and receiving a single String data
The above is a scenario. When requesting data, there are usually two data sources, cloud or local, but the speed of obtaining data is inconsistent. The Select function is to make a choice to see which end of the data comes the fastest, so whose data is used for rendering
fun getLocal() = viewModelScope.async { delay(2000) "This is local data" } fun getNet() = viewModelScope.async { delay(1000) "This is the data of the network" }
5.1 traditional methods
If you want to decide which data to render according to the speed of the data returned by which api, you can't do it through one collaboration. getLocal and getNet are asynchronous. When you get the data, you can get all the data only after the slowest return. Therefore, you can only open two collaborations to monitor their data returns separately
5.2 select
Then, in the select function, you do not need to open up two coroutines to achieve the effect of two coroutines
viewModelScope.launch { val result = select<Response<String>> { getLocal().onAwait{Response(it)} getNet().onAwait{Response(it)} } Log.e("TAG","${result.t}") }
Look, we don't use await here, but onAwait function, which is equivalent to registering a callback in select. No matter which await returns data first, it will call back the data
class Response<T>(var t:T)
In this way, one collaborative process is used to complete what can be done by two collaborative processes
5.3 multi Channel multiplexing
Similar to await multiplexing, multiple channels send data, and the data will be printed if any channel is detected to send data
fun CoroutineScope.channel1() = produce<String> { delay(200) send("123") } fun CoroutineScope.channel2() = produce<String> { delay(400) send("123456") }
viewModelScope.launch { val result = select<Response<String>> { channel1().onReceive { Response(it) } channel2().onReceive { Response(it) } } Log.e("TAG", "${result.t}") }
This is a bit similar to await. Instead of using receive, onReceive callback is used
5.4 SelectClause type resolution
Since the select function is used above, which events can be selected?
From SelectBuilder, you can see that events of type SelectClause0, SelectClause1 and SelectClause2 can be selected
public interface SelectBuilder<in R> { /** * Registers a clause in this [select] expression without additional parameters that does not select any value. */ public operator fun SelectClause0.invoke(block: suspend () -> R) /** * Registers clause in this [select] expression without additional parameters that selects value of type [Q]. */ public operator fun <Q> SelectClause1<Q>.invoke(block: suspend (Q) -> R) /** * Registers clause in this [select] expression with additional parameter of type [P] that selects value of type [Q]. */ public operator fun <P, Q> SelectClause2<P, Q>.invoke(param: P, block: suspend (Q) -> R) /** * Registers clause in this [select] expression with additional nullable parameter of type [P] * with the `null` value for this parameter that selects value of type [Q]. */ public operator fun <P, Q> SelectClause2<P?, Q>.invoke(block: suspend (Q) -> R): Unit = invoke(null, block) /** * Clause that selects the given [block] after a specified timeout passes. * If timeout is negative or zero, [block] is selected immediately. * * **Note: This is an experimental api.** It may be replaced with light-weight timer/timeout channels in the future. * * @param timeMillis timeout time in milliseconds. */ @ExperimentalCoroutinesApi public fun onTimeout(timeMillis: Long, block: suspend () -> R) }
SelectClause0 event: event with no return value. For example, join, the corresponding onJoin is the SelectClause0 event, which is a parameterless constructor
fun CoroutineScope.co1() = launch { delay(100) Log.e(TAG,"co1 finish") } fun CoroutineScope.co2() = launch { delay(10) Log.e(TAG,"co2 finish") }
If there is no return value, it is of type Unit
select<Unit> { co1().onJoin{ print("co1 finish")} co2().onJoin{ print("co2 finish")} }
SelectClause1 event: like await, receive has a return value, and the corresponding onAwait and onReceive get the return value through callback
SelectClause2 event: the corresponding event has a return value and requires an additional parameter. For example, the send function of Channel needs to send a data, and the corresponding onSend needs to send a data
select<Unit?> { launch { delay(200) channel1().onSend("2000"){ print("send in $it") } } launch { delay(100) channel2().onSend("1000"){ print("send in $it") } } }
6 multiplexing of flow
For example, in the previous example, to get data from the local and network, you need to start two processes to get data. In fact, you can combine the two streams into one stream output through the merge operator
viewModelScope.launch { listOf(::getLocal,::getNet) .map { kFunction0 -> //Function executed kFunction0.invoke() }.map { function -> flow { emit(function.await()) } }.merge().collect { Log.e(TAG,"result: $it") } }
7 concurrency security of collaborative processes
Like multithreading in Java, the concurrency of multiple processes will also bring security problems
Solutions: Channel, Mutex (locking), semaphore, etc., which are consistent with the processing methods in Java