I'm trying to understand the basic concepts about ...
# coroutines
m
I'm trying to understand the basic concepts about coroutines. The docs for
Job
state this:
Creates a job object in an active state. A failure of any child of this job immediately causes this job to fail, too, and cancels the rest of its children.
this makes me think that if I have 2 jobs
a
and
b
`launch`ed from the
CoroutineScope
of
Junk
, and said scope's context is just a
Job()
...
Copy code
class Junk : CoroutineScope {
    override val coroutineContext: CoroutineContext = Job()
}

// ...

val junk = Junk()

val a = junk.launch { /*...*/ }

val b = junk.launch { /*...*/ }

b.cancel()

joinAll(a, b)
...then cancelling
b
should cancel
a
and viceversa. But that doesn't happen.
b
gets cancelled quietly, and
a
does its thing (the full version of this snippet has
delay(1000); println("hello from...")
in each coroutine) If "failing" is not cancelling, then... it's to have an unhandled exception? (If that's the case I don't understand the practical difference between a
Job
and a
SupervisorJob
, but one thing at a time)
a
Two things that might help: 1. Cancelling is normal, and not considered a failure. Cancelling a child doesn’t affect the parent job, or sibling jobs. Take a look at the docs for Job https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-job/
A coroutine that threw CancellationException is considered to be cancelled normally. If a cancellation cause is a different exception type, then the job is considered to have failed.
2.
launch {}
means a new
Job()
is created - with a parent based on the current coroutine context. https://kotlinlang.org/docs/coroutine-context-and-dispatchers.html#children-of-a-coroutine
When a coroutine is launched in the
CoroutineScope
of another coroutine, it inherits its context via
CoroutineScope.coroutineContext
and the
Job
of the new coroutine becomes a child of the parent coroutine’s job.
m
Oh yeah thanks. I was aware that
launch
and other functions (all of them?) to create coroutines always their own
Job
. Also I realized I'm just dumb and while writing a snippet to show you what I didn't get about
Job
vs
SupervisorJob
, I ended up understanding the difference. Which is exactly what's explained in the documentation, but my job
a
was completing successfully before `b`'s exception would reach its parent.
Thanks for answering. I'm leaving the difference here because I hate it when I find "yeah I solved it" on the web without the explanation:
Copy code
val a = junk.launch {
    delay(1000)
    println("Hello from a")
}

val b = junk.launch {
    delay(500)
    throw Exception()
    println("Hello from b")
}

joinAll(a, b)
Copy code
class Junk : CoroutineScope {
    override val coroutineContext: CoroutineContext = Job() + CoroutineExceptionHandler {_, _ -> println("A coroutine failed")}
}
So, this makes both `junk`'s and `a`'s jobs fail, as intended. Replacing `Junk`'s for `SupervisorJob`:
Copy code
class Junk : CoroutineScope {
    override val coroutineContext: CoroutineContext = SupervisorJob() + CoroutineExceptionHandler {_, _ -> println("Falló")}
}
gives a different result, where only
b
fails Ok thanks for your time, testing concurrency is hard.
a
my pleasure! Good to see you understand it better now. I like the idea that there are 4 types of documentation, so it’s nice to have different ways that explain the same thing 👍