Ignoring offers to coroutine channels after closing

Viewed 2290

Is there a good way to have channels ignore offers once closed without throwing an exception?

Currently, it seems like only try catch would work, as isClosedForSend isn't atomic.

Alternatively, is there a problem if I just never close a channel at all? For my specific use case, I'm using channels as an alternative to Android livedata (as I don't need any of the benefits beyond sending values from any thread and listening from the main thread). In that case, I could listen to the channel through a producer that only sends values when I want to, and simply ignore all other inputs.

Ideally, I'd have a solution where the ReceiveChannel can still finish listening, but where SendChannel will never crash when offered a new value.

2 Answers

Channels throw this exception by design, as means of correct communication.

If you absolutely must have something like this, you can use an extension function of this sort:

private suspend fun <E> Channel<E>.sendOrNothing(e: E) {
    try {
        this.send(e)
    }
    catch (closedException: ClosedSendChannelException) {
        println("It's fine")
    }
}

You can test it with the following piece of code:

val channel = Channel<Int>(capacity = 3)
    launch {

        try {
            for (i in 1..10) {
                channel.sendOrNothing(i)
                delay(50)
                if (i == 5) {
                    channel.close()
                }
            }

            println("Done")
        }
        catch (e: Exception) {
            e.printStackTrace()
        }
        finally {
            println("Finally")
        }
    }

    launch {
        for (c in channel) {
            println(c)
            delay(300)
        }
    }

As you'll notice, producer will start printing "It's fine" since the channel is closed, but consumer will still be able to read first 5 values.

Regarding your second question: it depends.

Channels don't have such a big overhead, and neither do suspended coroutines. But a leak is a leak, you know.

I ended up posting an issue to the repo, and the solution was to use BroadcastChannel. You can create a new ReceiveChannel through openSubscription, where closing it will not close the SendChannel.

This more accurately reflects RxJava's PublishSubject

Related