https://kotlinlang.org logo
#coroutines
Title
# coroutines
g

gotoOla

06/25/2020, 2:15 PM
Hi! If I have a class that is extending CoroutineScope as
Copy code
QueueReader : CoroutineScope by CoroutineScope(<http://Dispatchers.IO|Dispatchers.IO>) { abstract fun processMessage(msg: Message) }
and then I have an implementation
Copy code
RabbitQueueReader: QueueReader { override processMessage(msg: Message) { // do the work } }
shouldn't my RabbitQueueReader here also utilize the IO dispatcher? In my logs it seems that it is the default dispatcher that is used in the processMessage function
from the QueueReader, this is how the processMessage is called:
Copy code
private fun CoroutineScope.launchMessageProcessor() = launch { 
   for(messages in internalChannel) {
        messages.map.async { processMessage(it) }
   }
}
and the launchMessageProcessor is started in an init function, the internal channel is a private
Channel<List<Message>>
z

Zach Klippenstein (he/him) [MOD]

06/25/2020, 2:22 PM
Implementing
CoroutineScope
directly is considered a bad practice, better to have a private val store the scope and reference it explicitly. Which init function is
launchMessageProcessor
invoked from? And you’re not using the result from
messages.map.async
, so that should be
launch
as well.
g

gotoOla

06/25/2020, 2:29 PM
I redacted some information for this example, the Defferred result is used so that's not an issue 🙂 What you are saying about implementing coroutinescope is interesting though, could you explain a bit more here? I am not really implementing it since I do the
Copy code
CoroutineScope by CoroutineScope(<http://Dispatchers.IO|Dispatchers.IO>)
My presumption (which likely is wrong here) is that that would make my class "become" the CoroutineScope.IO, so when I do a
launch
or
async
that would be the coroutinescope (and the dispatcher) that is used (the fact that I could omit the GlobalScope.-prefix also made me feel that I "had" the expected IO-scope there)
The
laucnhMessageProcessor
is called from a start-function that lies within the
QueueReader
,
Copy code
// Code in QueueReader
    fun start(runForever: Boolean = true) {
                            val processors = (1..numberOfProcessors).map {
                <http://logger.info|logger.info>("Starting processing channel.")
                launchMessageProcessor()
            }
        )
    }
and then that is invoked from my RabbitQueueReader
z

Zach Klippenstein (he/him) [MOD]

06/25/2020, 2:35 PM
It’s recommended not to have
class YourClass : CoroutineScope
, instead do
Copy code
class YourClass {
  private val scope: CoroutineScope
But yea that looks like the coroutine should use the IO dispatcher
How are you logging which dispatcher is being used?
g

gotoOla

06/25/2020, 2:38 PM
I'm logging via slf4j in a backend service, it's that coroutine-logging-configuration that gives you
14:27:16.188 [DefaultDispatcher-worker-2]
out of curiosity, why is it that one would prefer having coroutine scope by delegation over inheritance?
hm, maybe I am just thrown off by the logging here cause if I debug the actual this.coroutineContext it seems the parallelism is 64
e

Evan R.

06/25/2020, 2:52 PM
Check out the docs for the IO dispatcher:
Copy code
This dispatcher shares threads with a Default dispatcher, so using withContext(<http://Dispatchers.IO|Dispatchers.IO>) { ... } does not lead to an actual switching to another thread — typically execution continues in the same thread.
It is likely running on the IO dispatcher, but you’re seeing the default dispatcher’s name there because they both use the same threadpool. The difference between Dispatchers.Default and Dispatchers.IO is that Dispatchers.IO is designed for long-running, blocking tasks and will spin up new threads if there are not enough threads in the pool. Dispatchers.Default has a fixed number of threads equal to the number of CPU cores on your machine, or at least two.
☝️ 1
u

uli

06/25/2020, 3:01 PM
relying on inheritance in
init
is generally dangerous! I guess your scope could have not yet been initialised
z

Zach Klippenstein (he/him) [MOD]

06/25/2020, 3:03 PM
Yea, thread != dispatcher, although I thought the IO dispatcher changed the thread name when it took over the thread. Maybe not?
This article explains why storing scope in a property is better: https://proandroiddev.com/why-your-class-probably-shouldnt-implement-coroutinescope-eb34f722e510 tl;dr: The fact that
QueueReader
needs to use a
CoroutineScope
internally is a private implementation detail of that class, but by implementing the interface you’re making it part of its public API.
g

gotoOla

06/26/2020, 9:07 AM
sorry I feel asleep yesterday but thanks for all the input and info! The "init"-function I was talking about isn't the actual
init
-block but rather "an" init function (fun start()...) I think I was just thrown off by the log message as Evan R explained
I was expecting something like Default-IO-Dispatcher or something like that in the logs 😛
@Zach Klippenstein (he/him) [MOD] read that article but to me it's not telling the whole truth. That example is very context and lifecycle driven. In my case I don't implement CoroutineScope as such (I am extending an existing implementation) and my use case is for backend development in a Service class (singleton) that has the lifecycle of the backend application.
Copy code
But now we've made it also uphold the contract of the CoroutineScope interface, opening up an entirely different world of functions.
I understand that in a ViewModel you wouldn't want to expose that but I might actually want my consumers (e.g. the classes who are extending this) to be aware that they are a CoroutineScope 🤔 hm... I'll put some more thought into this
I guess what I am fishing for here is that when you say that it's considered bad practice, would that be in the ecosystem of Android with their fragments, activities, different lifecycle etcetera? 😛
6 Views