I know what a job is (handle to a coroutine), what a Dispatcher is (threadpool it runs on) and what a CoroutineScope is (well, a scope or we could also say a kind of group) , yet I don't understand this syntax:
val scope = CoroutineScope(Job() + Dispatchers.Main)
Why does one pass in Job()
? What is it that I pass to CoroutineScope
anyways, is it a lambda? As far as I know a new job gets created each time one does something like this anyways:
val job = scope.launch { ... }
So why does one pass in a single instance of Job
to CoroutineScope in CoroutineScope(Job() + Dispatchers.Main)
?
It's a confusing overload of the meaning of what a Job is. Every coroutine Job has a parent Job. Even the top-most coroutines launched directly from a CoroutineScope have a parent Job, and in this case, it's a Job that isn't a coroutine.
The Job you're passing to the CoroutineScope "constructor*" is the parent Job of coroutines that are launched directly by that scope. It doesn't represent a coroutine itself, but it will have child coroutines.
A CoroutineContext always includes a Job that is a parent of the current coroutine, and always includes a Dispatcher that manages the threads being used. As you go deeper into a coroutine's nested lambdas, the CoroutineContext may be locally modified by inner launch
, async
, and withContext
blocks.
The code Job() + Dispatchers.Default
creates a CoroutineContext that has these two elements.**
It's actually unnecessary to pass a plain Job()
to the CoroutineScope constructor, because if you omit it, one will be generated anyway since it's a required element.
What is much more common is to pass a SupervisorJob()
to be part of that default CoroutineContext. This type of Job allows its child coroutines to fail independently from each other (one failed coroutine won't cause the rest of them to be cancelled). It's very common to want this behavior from a CoroutineScope that will be used to run multiple coroutines that may not be interdependent. This is how lifecycleScope
and viewModelScope
are created under the hood on Android.
Besides the Job and Dispatcher, I think it's a good idea to also use + CoroutineName("...")
so your error logs will be more helpful.
*It's actually just a function that looks like a constructor.
**A CoroutineContext behaves like an immutable Map, where its keys are the companion objects of Job, CoroutineInterceptor (supertype of Dispatcher), CoroutineName, and CoroutineExceptionHandler. When you use +
on a CoroutineContext Element, it merges them together into a new CoroutineContext that still has one value for each of those keys. I think it's possible to create your own Keys and use them to attach extra data that is passed down into your coroutines, but I have never tried this.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With