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.Dispatchers.Default
to launch, you're basing your scope on GlobalScope
which will already use that dispatcher.do 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.send
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.offer
with unlimited channel, instead of launching new coroutine for every send
.Tash
09/29/2019, 9:41 PM