Coroutines can be cancelled, which will stop the code within the coroutine from further execution. Coroutine cancellations aren’t related to the thread terminations that we’re used to in Java. Cancellations are lightweight and have effect across the hierarchy of coroutines that share context.

Both the Job object, which is returned by launch(), and the Deferred<T> object, which is returned by async(), have a cancel() and a cancelAndJoin() method. We can use these methods to explicitly cancel a coroutine, but there’s a catch. A coroutine is cancelled only if it is currently in a suspension point. If a coroutine is busy executing code, it won’t receive the cancellation notice and may not bail out. Let’s discuss this further and find ways to deal with this behavior.

Kotlin provides structured concurrency, where coroutines that share a context form a hierarchical relationship. Coroutines that belong to a hierarchy follow some rules and exhibit prescribed behaviors:

  • A coroutine is considered to be a child of another coroutine if it shares the context of the coroutine that creates it.

  • A parent coroutine completes only after all its children complete.

  • Cancelling a parent cancels all its children.

  • A coroutine that has entered a suspension point may receive a CancellationException thrown from the suspension point.

  • A coroutine that is busy, that’s not in a suspension point, may check an isActive property to see if it was cancelled when it was busy.

  • If a coroutine has resources to clean up, that needs to happen within the finally block within the coroutine.

  • An unhandled exception will result in the cancellation of a coroutine.

  • If a child coroutine fails, it will result in the parent coroutine cancelling and thus result in cancellation of the coroutine’s siblings. You can modify this behavior using supervised jobs that make cancellation unidirectional, from parent to children.

That’s a lot of information in one place, but you can refer back to this when working with coroutines. Let’s examine these ideas with some examples.

Cancelling coroutines

If we don’t care for the completion of a task that we started in a coroutine, we may cancel it by calling cancel() or cancelAndJoin() on the Job or the Deferred<T> instance. However, the coroutine may not budge to that command right away. If the coroutine is busy executing, the command doesn’t interrupt it. On the other hand, if the coroutine is parked in a suspension point, like in a call to yeild(), delay(), or await(), then it will be interrupted with a CancellationException.

When designing a coroutine, keep the previous constraints in mind. If you’re performing a long-running computation, you may want to structure it so that you take a break frequently to check if the isActive property of the coroutine is still true. If you find it to be false, you can break out of the computation, honoring the request to cancel.

Sometimes you may not have the capability to check the isActive property because the function you internally call within the coroutine is blocking and doesn’t involve a suspension point. In this case, you may consider delegating that blocking call to another coroutine—a level of indirection—and await on it, thus introducing a suspension point around your blocking call.

Let’s explore these options with an example. We’ll verify the behavior of the code that plays nicely with cancellation and also code that doesn’t. This will give us a clear view of how we should approach the design of code with a coroutine, from the cancellation point of view.

We’ll first create a compute() function that runs in one of two modes: if the parameter passed in is true, it will check the isActive property in the middle of a long-running computation; if that parameter is false, it will run wild for a long time. Since we need access to the isActive property, the code needs to run in the context of a coroutine. For this, we’ll wrap the code within the compute() function inside a call to coroutineScope() that carries the scope from the caller.

Here’s the code for the compute() function:

Get hands-on with 1200+ tech skills courses.