Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Understanding CoroutineScope(Job() + Dispatchers.Main) syntax

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)?

like image 992
stefan.at.wpf Avatar asked Sep 17 '25 12:09

stefan.at.wpf


1 Answers

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.

like image 62
Tenfour04 Avatar answered Sep 19 '25 05:09

Tenfour04