Tash
09/28/2019, 5:35 AMnotify(...) on an object of that class and it internally, with back-pressure awareness, processes and runs some computations on each notification.
This is on Android, and for this example creating an appScope by using GlobalScope + CoroutineExceptionHandler because I want to treat the processing of the notifications as “fire-and-forget”. Also trying to use consumeAsFlow() + collect() on the Channel:
class ChannelExample {
private val appScope = GlobalScope + CoroutineExceptionHandler { coroutineContext, throwable ->
// Log message + throwable
}
private val numbersChannel = Channel<Int>()
init {
appScope.launch(Dispatchers.Default) {
numbersChannel
.consumeAsFlow()
.collect { number -> handleNumber(number)}
}
}
fun notifyNumber(number: Int) {
appScope.launch {
numbersChannel.send(number)
}
}
private suspend fun handleNumber(number: Int) {
try {
// Do some computations
} catch (ce: CancellationException) {
throw ce
} catch (ex: Exception) {
// Log message + exception
}
}
}
1) do I need to call close() or cancel() on numbersChannel?
- if yes, where would be the best place to do that?
- if no, why not?
2) are there any other things in this example that stray from best practices?
3) are there better ways to achieve my original use case?
Thanks!Dominaezzz
09/28/2019, 10:19 AMnotifyNumber should just use offer.Zach Klippenstein (he/him) [MOD]
09/29/2019, 4:15 PMconsumeAsFlow().collect, just use consumeEach directly.Zach Klippenstein (he/him) [MOD]
09/29/2019, 4:16 PMDispatchers.Default to launch, you're basing your scope on GlobalScope which will already use that dispatcher.Zach Klippenstein (he/him) [MOD]
09/29/2019, 4:18 PMZach Klippenstein (he/him) [MOD]
09/29/2019, 4:32 PMdo I need to callNot necessarily, but you should at least ensure the coroutine you launch in the init block is cleaned up and doesn't leak. Usually the best approach here is to pass the base scope in through the constructor so this class doesn't have to concern itself with lifetime. In other words, make use of structured concurrency. Then whatever is instantiating this class is responsible for ensuring that a scope with the right lifetime is used. E.g. if this is instantiated by anorclose()oncancel()?numbersChannel
Application, your Application class can probably just pass GlobalScope since it is a process-scoped Singleton. And in your unit tests, you can pass in a scope that gets cancelled after every test.
If you do that, then because you're using the consume* APIs the channel will automatically get cancelled when the scope is.Tash
09/29/2019, 9:34 PMRendezvousChannel. If i understand correctly, every time notifyNumber() is invoked the coroutine that sends on the channel will suspend until the receiver is ready to receive more items…Hence I was also unsure about the difference in this case of using `send`/`offer`
re: `GlobalScope`: Yes that’s correct; in the actual project, the scope will be passed into the class’s constructor through a DI setup that provides GlobalScope.
re: "If you do that, then because you're using the consume* APIs the channel will automatically get cancelled when the scope is." : Ahh that’s what I was not sure about, thanks for clarifying! Was confused with the relationship of scope + channel. Will just use the consumeEach directly.Tash
09/29/2019, 9:35 PMsend vs offer specifically for a RendezvousChannel?Dominaezzz
09/29/2019, 9:40 PMoffer drops the item if there's no coroutine calling receive. send waits for receive to be called.Dominaezzz
09/29/2019, 9:40 PMoffer with unlimited channel, instead of launching new coroutine for every send.Tash
09/29/2019, 9:41 PM