Reputation: 2769
Can anyone explain the difference between them? I think scope provides a reference(e.g. Job) to cancel them and context provides a reference to underlying thread. Is that so?
Upvotes: 98
Views: 32548
Reputation: 29884
Yes, in principle you are right, here more details.
Scope
viewModelScope
in Android) to avoid leakingContext
The coroutine context is a set of various elements. The main elements are the Job of the coroutine, which we've seen before, and its dispatcher [...]. (Source)
In case you specify a dispatcher there are four options which basically determine on which thread the coroutines will run:
Dispatchers.Default
- for CPU intense work (e.g. sorting a big list)Dispatchers.Main
- what this will be depends on what you've added to your programs runtime dependencies (e.g. kotlinx-coroutines-android
, for the UI thread in Android)Dispatchers.Unconfined
- runs coroutines unconfined on no specific threadDispatchers.IO
- for heavy IO work (e.g. long-running database queries)The following example brings both scope and context together. It creates a new scope in which the coroutines will run (if not changed) on a thread designated for IO work and cancels them via their scope.
val scope = CoroutineScope(context = Dispatchers.IO)
val job = scope.launch {
val result = suspendFunc1()
suspendFunc2(result)
}
// ...
scope.cancel() // suspendFunc1() and suspendFunc2() will be cancelled
Upvotes: 72
Reputation: 200246
They are indeed closely related. You might say that CoroutineScope
formalizes the way the CoroutineContext
is inherited.
CoroutineScope
has no data on its own, it just holds a CoroutineContext
. Its key role is as the implicit receiver of the block you pass to launch
, async
etc.
See this example:
runBlocking {
val scope0 = this
// scope0 is the top-level coroutine scope.
scope0.launch {
val scope1 = this
// scope1 inherits its context from scope0. It replaces the Job field
// with its own job, which is a child of the job in scope0.
// It retains the Dispatcher field so the launched coroutine uses
// the dispatcher created by runBlocking.
scope1.launch {
val scope2 = this
// scope2 inherits from scope1
}
}
}
You can see how the CoroutineScope
mediates the inheritance of coroutine contexts. If you cancel the job in scope1
, this will propagate to scope2
and will cancel the launch
ed job as well.
Note the key syntactical feature: I explicitly wrote scope0.launch
, but had I written just launch
, it would implicitly mean exactly the same thing. This is how CoroutineScope
helps to "automatically" propagate the scope.
Upvotes: 62
Reputation: 82057
CoroutineScope
has-a CoroutineContext
.
For example if you have:
runBlocking { // defines coroutineScope
launch(Dispatchers.Default) { //inherits coroutineScope but changes context
}
}
runBlocking
defines a CoroutineScope
(learn about it here) which launch
inherits. The context is being overridden by explicitly specifying a dispatcher here. If you look at the definition of launch
, you can see that it takes an optional CoroutineContext
:
public fun CoroutineScope.launch(
context: CoroutineContext = EmptyCoroutineContext,
...
)
Another part of the context would be the coroutine's name:
launch(CoroutineName("launchMe") + Dispatchers.Default) {
println("")
}
Upvotes: 22