Coroutines: Context, Cancellation, SupervisorJob scenarios (Part 1)

Mayank Mehta
4 min readMay 22, 2020

--

Photo by Rod Long on Unsplash

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 Elements which are Dispatchers, CoroutineName, Job and CoroutineExceptionHandler.

Source: https://youtu.be/w0kfnydnFWI?t=402

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.

https://github.com/Kotlin/kotlinx.coroutines/blob/master/kotlinx-coroutines-core/common/src/Job.kt#L35

1. When parentScope is cancelled, all children will get cancelled.

Here children being all coroutines launched using parentScope.

Source https://youtu.be/w0kfnydnFWI?t=474

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

https://youtu.be/w0kfnydnFWI?t=537
  • Siblings are not cancelled
  • parentScope is not cancelled

3. When join is called after Job is cancelled

  • It suspends unless cancelled Coroutine 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 Exceptions. How can they be caught and what happens to CoroutineScope.

  1. When CoroutineScope uses default CoroutineExceptionHandler

Default CoroutineExceptionHandler ignores CancellationException, on any other Exception does the following

  • Sibling Jobs will get cancelled. parentScope will get cancelled and Exception 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 unless await 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.

Photo by Jackson Simmer on Unsplash

SupervisorJob

CoroutineScope that needs to survive when child Job fails should have its CoroutineContext created with SupervisorJob and customCoroutineExceptionHandler.

  1. 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

https://youtu.be/w0kfnydnFWI?t=1427

In this sample code when Job1 fails with an Exception

  • parentScope and siblings Jobs would not be cancelled.

Thank you for reading, Happy Coding!

--

--