Cancelling Channel.send() that runs in a runBlocking block

I have code that tries to send to a channel within a callback that isn’t a suspend function. I use runBlocking for this, along with a SupervisorJob. Example:

val job = SupervisorJob()
val channel = Channel()

[...]

fun onEvent() {
    runBlocking {
        try {
            withContext(job) {
                channel.send(someData)
            }
        } catch (_: CancellationException) {
            // don't propagate cancellation; let runBlocking finish gracefully
        } catch (_: ClosedSendChannelException) {
        }
    }
}

[...]

// When shutting down, I do this:
channel.close()
job.cancel()

I am focusing on the withContext block here. As the send() documentation states:

Closing a channel after this function has suspended does not cause this suspended send invocation to abort, because closing a channel is conceptually like sending a special “close token” over this channel.

The job.cancel() call is intended to handle the case where the send() call is already suspended. The outer try-catch block then catches the CancellationException to avoid an error in runBlocking. ClosedSendChannelException is also caught in case the channel got closed before send() was called.

Now, is this okay? Or are there better ways to cancel such a suspending function call that runs inside a runBlocking block?

The best way to achieve what you’re trying do IMHO is to use the callbackFlow operator, and use a cold flow to listen to the events.

Othersise consider adding a buffer to the channel and use the non-suspending version Channel.trySend to send the elements immediately. The buffer size may be unlimited or conflated or whatever fits your need.

If you have multiple users listening to the channel, prefer a SharedFlow (similar to the channel, but broadcast)

To stop the emission of the events:
In case of the callbackFlow, it’s done automatically when you cancel the collection (remember to register correctly awaitClose).

In case of channel and sharedFlow, the best is to remove the callback first, and then close the channel. Since you use a non-suspending trySend, you don’t need to catch any exception.

The onEvent I guess it’s a hot source of callbacks, right? In that case the Channel/SharedFlow solution is the most appropriate IMHO