I have a question about coroutine scopes (or is it...
# coroutines
s
I have a question about coroutine scopes (or is it contexts?). When I need to launch a new coroutine from a
suspend
function, I usually wrap the
launch
or
async
call with
withContext
(although, it looks like I should be using
coroutineScope
a little more often). This will tie the new coroutines into the structured concurrency of the calling coroutine, which is all fine and dandy. On the other hand, suppose I had something along the lines of
Copy code
shortLivedScope.launch { 
    // some stuff
    longLivedScope.launch { performSomeTasks() }
    // some stuff
}
What is the relationship between the coroutines I have now launched?
a
Potentially none. It depends on whether those scopes are related.
💯 1
s
Maybe my blog post may help you, the "Switching CoroutineScopes" part. https://link.medium.com/vzSsBZlo1ab
s
Thanks, it's mostly clear now. A few clarifications, though: 1. If the inner coroutine (in
longLivedScope
) is canceled, then the outer coroutine wouldn't be canceled in my example unless I add a
join
call, correct? 2. In my example, will the outer coroutine finish if the inner one is still running? So if I call
join
on the outer coroutine, and it reaches the end, but the inner coroutine is still running, will the
join
call suspend? My instinct is that it will not suspend, but if the inner coroutine was started without an explicit scope (just an unadorned
launch
), then the outer
join
would suspend. All of this assumes the scopes are otherwise unrelated (which is a good consideration that I was taking for granted)
a
There's no such thing as an unadorned launch, it's an extension function of
CoroutineScope
. It just so happens that
launch
,
async
and
coroutineScope
all have a
CoroutineScope
receiver to their lambda blocks, so you can call it with an implicit
this
in those places.
If a child coroutine is cancelled, it never cancels the parent. A cancelled parent always cancels all children.
Calling
join
never causes cancellation and does not affect the propagation behavior of cancellation.
s
Oh, right. But
async/wait
propagates a cancellation (as in the linked article) because there's no value received, just a thrown
CancellationException
that propagates up the chain. So
launch/join
and
async/await
handle cancellations differently (which makes sense because they handle exceptions differently, and cancellation is implemented with an exception).
a
"propagates" in a loose sense, there's no explicit communication between jobs the way there is when a parent fails or is cancelled and it cancels children, or when a child fails and the failure propagates to the parent job. Only in the sense that any unhandled CancellationException will cancel a coroutine. It's no different than if you
throw CancellationException()
yourself.
it may seem like splitting hairs, but it's the difference between whether
Copy code
try {
  someDisjointDeferred.await()
} catch (ce: CancellationException) {
  delay(timeout)
  tryAgain()
}
will work, or if the
delay
in the catch will throw another
CancellationException
because the calling job is cancelled
s
Right. Good call.