Skip to content

Channels ‐ Communication between coroutines

Devrath edited this page Dec 23, 2023 · 1 revision

Sending & receiving values in channel

  • Whenever we send multiple values using channel.send() on the sender side, We need to trigger channel.recieve() for the same amount of time on the receiving end.
  • If say trigger send() thrice and have recieve() on the receiver only twice, The receiver will not get the third item.

Cancelling a channel

Observation

  • If we cancel the channel, even if the channel sends new emissions, it is not received by the receiver.
  • When the channel is canceled, an exception is thrown and you can also throw a custom exception.
  • You can catch the exception, It won't cause a runtime crash of the application.
  • Once you cancel the channel, You cannot send and receive the emissions.
  • Even if you send values after the channel is canceled it won't take any effect.
  • Invoking cancel() on a coroutine job associated with a channel will cancel the entire coroutine and any ongoing operations within it.
  • This includes canceling both the sender and receiver coroutines involved in channel communication.

Sample Code

@HiltViewModel
class ChannelsDemoVm @Inject constructor( ) : ViewModel() {

    // A channel of languages
    private var languageChannel = Channel<Languages>()

    /**
     * Canceling a channel
     */
    fun cancellingChannel() {

        // Co-Routine - 1
        viewModelScope.launch {
            println("Sending value - 1")
            languageChannel.send(Languages.English)
            println("Sending value - 2")
            languageChannel.send(Languages.Hindi)
            println("Sending value - 3")
            languageChannel.send(Languages.French)
        }

        // Co-Routine - 2
        viewModelScope.launch {
            try {
                println(languageChannel.receive())
                languageChannel.cancel(CancellationException("Custom Exception"))
                println(languageChannel.receive())
                println(languageChannel.receive())
            }catch (ex : Exception){
                println(ex.message)
                println("Is it closed for Send: -> "+languageChannel.isClosedForSend)
                println("Is it closed for Receive: -> "+languageChannel.isClosedForReceive)
            }
        }

    }

    enum class Languages { English , Hindi , French }

}

Output

Sending value - 1
English
Custom Exception
Is it closed for Send: -> true
Is it closed for Receive: -> true
Sending value - 2

Closing a channel

  • We use close to close the channel when there are no more emissions.
  • We try to send emissions once the channel is closed, It will lead to a runtime exception and might cause a crash.
  • When a channel is closed, attempts to send further elements will result in an exception (ClosedSendChannelException).
  • The receiving side can use the isClosedForReceive property to check whether the channel has been closed.

Choosing between close() and cancel()

  • Depends on the use case
  • If you want to explicitly signal the end of data transmission through the channel, allowing the receiver to gracefully handle the termination of communication, use close().
  • If you need to abruptly stop the entire coroutine, including both the sender and receiver, use cancel(). This can be useful, for example, when you want to interrupt the coroutine in response to external events or timeouts.
  • In many cases, close() is sufficient for signaling the end of communication through the channel. cancel() is more aggressive and can be used when you want to cancel the entire coroutine, including any ongoing operations, due to external conditions or certain constraints in your application logic.

Code

@HiltViewModel
class ChannelsDemoVm @Inject constructor( ) : ViewModel() {

    // A channel of languages
    private var languageChannel = Channel<Languages>()

    /**
     * Sending and receiving only one value
     */
    fun simpleCommunication() {

        // Co-Routine - 1
        viewModelScope.launch {
            languageChannel.send(Languages.English)
        }

        // Co-Routine - 2
        viewModelScope.launch {
            println(languageChannel.receive())
        }

    }

    /**
     * Sending and receiving multiple values
     */
    fun sendingMultipleValues() {

        // Co-Routine - 1
        viewModelScope.launch {
            languageChannel.send(Languages.English)
            languageChannel.send(Languages.Hindi)
            languageChannel.send(Languages.French)
        }

        // Co-Routine - 2
        viewModelScope.launch {
            languageChannel.consumeEach {
                println(languageChannel.receive())
            }
        }

    }


    /**
     * Canceling a channel
     */
    fun cancellingChannel() {

        // Co-Routine - 1
        viewModelScope.launch {
            println("Sending value - 1")
            languageChannel.send(Languages.English)
            println("Sending value - 2")
            languageChannel.send(Languages.Hindi)
            println("Sending value - 3")
            languageChannel.send(Languages.French)
        }

        // Co-Routine - 2
        viewModelScope.launch {
            try {
                println(languageChannel.receive())
                languageChannel.cancel(CancellationException("Custom Exception"))
                println(languageChannel.receive())
                println(languageChannel.receive())
            }catch (ex : Exception){
                println(ex.message)
                println("Is it closed for Send: -> "+languageChannel.isClosedForSend)
                println("Is it closed for Receive: -> "+languageChannel.isClosedForReceive)
            }
        }

    }


    /**
     * Closing a channel
     */
    fun closingChannel() {

        // Co-Routine - 1
        viewModelScope.launch {
            languageChannel.send(Languages.English)
            languageChannel.close()
            languageChannel.send(Languages.Hindi)
            languageChannel.send(Languages.French)

        }

        // Co-Routine - 2
        viewModelScope.launch {
            languageChannel.consumeEach {
                println(languageChannel.receive())
            }
        }

    }


    enum class Languages { English , Hindi , French }

}
Clone this wiki locally