Abhi
03/02/2024, 1:51 PMfun main() = runBlocking<Unit> {
launch {
launch {
launch {
delay(1000)
}
delay(1000)
}
delay(1000)
}
}Jeff Lockhart
03/02/2024, 5:04 PMlaunch calls are being called from the same CoroutineScope receiver from runBlocking. Each call to launch creates a new coroutine with its own Job, which is returned by the call. That Job is a child of the parent runBlocking scope's Job.
If you were to include additional context in the launch calls, e.g. launch(Dispatchers.Main + CoroutineName("coroutine-A")) { ..., that context would be merged with the parent scope's context from runBlocking (by default here, just EmptyCoroutineContext), which would be the coroutine's parent context. That parent context plus the child Job is equal to the child context, which makes up the child scope.
It's the Job that defines when a CoroutineScope is cancelled. And that Job potentially has a parent/child relationship with other `Job`s in order to propagate cancellation. A SupervisorJob is unique in that it does not propagate its cancellation. For this reason, a top-level CoroutineScope is often defined with a SupervisorJob.Jeff Lockhart
03/02/2024, 5:10 PMfun main() = runBlocking<Unit>(CoroutineName("foo")) {
// Dispatchers.Main + CoroutineName("foo") + job1
val job1 = launch(Dispatchers.Main) {
// CoroutineName("bar") + job2
val job2 = launch(CoroutineName("bar")) {
// CoroutineName("bar") + job3
val job3 = launch {
delay(1000)
}
delay(1000)
}
delay(1000)
}
}Abhi
03/03/2024, 10:33 AMAbhi
03/03/2024, 10:35 AMfun main(): Unit = runBlocking {
launch(CoroutineName("foo")) {
launch(CoroutineName("bar")) {
launch {
println(coroutineContext[CoroutineName])
}
}
}
}Abhi
03/03/2024, 10:35 AMAbhi
03/03/2024, 2:47 PMJeff Lockhart
03/03/2024, 3:03 PMfor job3 = launch { }, it would be // CoroutineName("bar") + job3?
Ah, yes. This is a good point because it shows how the parent context comes from the coroutine being launched from.