Using Coroutines? Then Keep These in Mind: Part 2 of 2

Masoud Fallahpour
4 min readJun 27, 2022
Photo by Maria Ionova on Unsplash

When using Kotlin, sooner or later, you come across coroutines. In two articles we’re going to review the most fundamental things you need to know when using coroutines. This is the second part. You can read the first part here.

For the rest of this article, I assume that you know the basics of coroutines.

Cancellation

This section is dedicated to a couple of notes about the cancellation behavior of coroutines.

Cancellation is cooperative

A coroutine has to cooperate to be cancellable. For example, if a coroutine is doing a CPU-intensive task and is not checking for its cancellation it continues its execution even if we call cancel() on its Job.

The following piece of code depicts a coroutine that cannot be canceled.

Running the above code produces the following output.

job: I'm sleeping 0 ...
job: I'm sleeping 1 ...
job: I'm sleeping 2 ...
main: I'm tired of waiting!
job: I'm sleeping 3 ...
job: I'm sleeping 4 ...
main: Now I can quit.

As can be seen after 1.3 seconds we call job.cancelAndJoin() to cancel the coroutine but the coroutine does not respect the cancellation and continues to print job: I’m sleeping 3 … and job: I’m sleeping 4…

To make the previous code cancellable we should use the isActive property which is an extension property available inside the coroutine.

The only change that we made was to replace while ( i < 5) with while(i < 5 && isActive). Now if you run the above code the output will be like the following.

job: I'm sleeping 0 ...
job: I'm sleeping 1 ...
job: I'm sleeping 2 ...
main: I'm tired of waiting!
main: Now I can quit.

Canceling a parent coroutine cancels its children

When a parent coroutine is canceled, all its children are canceled too. As an example look at the following code.

Running the above code generates the following output:

Canceling the parent coroutine...
Parent coroutine cancelled

In the above code, we cancel the parent coroutine after starting two child coroutines. This causes the children to be canceled hence no message is printed by them.

Canceling a child coroutine does not affect its siblings or its parent

When a child coroutine is canceled, it does not affect the execution of its parent or siblings. Look at the following piece of code as an example.

In the code above, we launch two child coroutines then after a second we cancel the first one. Canceling the first child coroutine has nothing to do with the second child coroutine and the second child continues its execution.

Running the above code gives the following output:

Launching two coroutines...
coroutine1: I'm started
coroutine2: I'm started
coroutine2: I'm done

Exceptions

Here, we’re going to talk about a couple of notes about the exception behavior of coroutines.

Coroutines throw CancellationException when canceled

When a coroutine is canceled it throws a CancellationException. There is no need to use a try/catch to catch this exception (as it’s ignored by the coroutine machinery) unless you want to do something when the coroutine is canceled. Take a look at the following code.

In the code above, we launch a coroutine then after a second we cancel it. Running the above code generates the following output.

Coroutine started.
Cancelling the coroutine...
Coroutine cancelled.

As can be seen, when the coroutine is canceled a CancellationException is thrown which is caught by the try/catch block.

Removing the try/catch block does not cause the program to crash because as stated earlier, the CancellationException is ignored by the coroutine machinery.

Exception in a child coroutine cancels its siblings and parent

When a child coroutine throws an exception (other than CancellationException) its siblings (if any) and parent coroutine are canceled altogether.

Consider the following code.

In the above code, we launch a coroutine with two children. After 500 milliseconds the first child coroutine throws an exception. This cancels both its sibling and parent coroutines.

If we run the above code we will get the following output.

coroutine1 started
coroutine2 started
coroutine2 canceled
Exception in thread "main" java.lang.RuntimeException
at ... (omitted for brevity)

Based on the above output, the parent and the sibling coroutines did not have the chance to print their respective messages because they got canceled when coroutine1 threw an exception.

It is possible to change this behavior so that when a child coroutine throws an exception, its sibling(s) and parent coroutines are not canceled. To read about how to implement this behavior take a look at here.

That’s it for the second and final part.

--

--

Masoud Fallahpour

Software engineering @ Klarna. Software engineer, *nix lover, curious learner, gym guy, casual gamer.