Coroutines: Context, Cancellation, SupervisorJob scenarios (Part 1)
This article is inspired from amazing talk about Coroutines given by Manuel Vivo and Florina Muntenescu during KotlinConf 2019, this is mainly for improving my understanding of the concepts explained in the talk.
In this article you will find reference to images from the talk and video link at that point in time. Code samples are various scenarios that I came across during the talk and I have added logs for more clarity. Feel free to modify the code and run in Kotlin Playground.
Recap from the talk
CoroutineScope are used for:
1. Keep track of coroutines
2. Ability to cancel ongoing work
3. Notify when failures happen
Job is used for:
1. Provide Lifecycle
2. Coroutine hierarchy
Job lifecycle properties:
1. isActive
2. isCompleted
3. isCancelled
CoroutineContext: Parent Vs Coroutine
To create an instance of CoroutineScope
you need to provide CoroutineContext
. To create an instance of CoroutineContext
you can provide it set of Element
s which are Dispatchers
, CoroutineName
, Job
and CoroutineExceptionHandler
.
Coroutines launched using parent scope will get all elements except Job
from parent context. However, inherited elements could be easily overridden as shown in the following code.
Coroutine cancellation
As best practice, cleaning up resource is ideal once task is over. Same applies in CoroutineLand. Whenever a coroutine is cancelled it throws CancellationException
which when propagated to parent would consider this as handled exception and would take no further action.
Use following table as reference to various coroutines lifecycles states.
1. When parentScope
is cancelled, all children will get cancelled.
Here children being all coroutines launched using parentScope
.
Once parentScope
is cancelled you can’t launch
new coroutines using that scope.
If you want to cancel just the children
and not the parentScope
you can do the following
2. When child Job
is cancelled
- Siblings are not cancelled
parentScope
is not cancelled
3. When join
is called after Job
is cancelled
- It
suspends
unless cancelledCoroutine
is completed.
4. Can’t await
on cancelled coroutine
All suspending function provided in kotlinx.coroutines
library support cancellation. Code snipped in this section use delay
so they get cooperative cancellation for free.
Read Kotlin docs to understand how to write coroutines that supports cancellation.
CoroutineExceptionHandler
In previous sections we discussed about coroutine cancellation, here we will focus on what happens when coroutines throws unexpected Exception
s. How can they be caught and what happens to CoroutineScope
.
- When
CoroutineScope
uses defaultCoroutineExceptionHandler
Default CoroutineExceptionHandler
ignores CancellationException
, on any other Exception
does the following
- Sibling
Job
s will get cancelled.parentScope
will get cancelled andException
would be propagated up. - If Exception is thrown from
launch
it will crash your application
- If Exception is throw from
async
it will not crash your application unlessawait
is called.
2. When CoroutineScope
is provided custom CoroutineExceptionHandler
- If Exception is thrown from
launch
it will not crash your application - However,
parentScope
will get cancelled.
3. Child coroutine gets CoroutineExceptionHandler
In the following code, parentScope
is created with custom CoroutineExceptionHandler
and when inner coroutine throws an Exception
it is caught. Refer back to first section in this post.
4. Don’t break the chain
Following code will crash the application even though it is launched with custom CoroutineExceptionHandler
reason being when Exception
is thrown from inner coroutine it will propagate the Exception
to its parent which in following code does not how to handle it.
SupervisorJob
CoroutineScope
that needs to survive when child Job
fails should have its CoroutineContext
created with SupervisorJob
and customCoroutineExceptionHandler
.
CoroutineScope
is created withSupervisorJob
but withoutCoroutineExceptionHandler
Following code will crash your android application since the exception
is not handled and parentScope
does not haveCoroutineExceptionHandler
.
2. CoroutineScope
is created withSupervisorJob
and CoroutineExceptionHandler
In this sample code when Job1
fails with an Exception
parentScope
and siblingsJob
s would not be cancelled.
Thank you for reading, Happy Coding!