DZone
Thanks for visiting DZone today,
Edit Profile
  • Manage Email Subscriptions
  • How to Post to DZone
  • Article Submission Guidelines
Sign Out View Profile
  • Post an Article
  • Manage My Drafts
Over 2 million developers have joined DZone.
Log In / Join
Please enter at least three characters to search
Refcards Trend Reports
Events Video Library
Refcards
Trend Reports

Events

View Events Video Library

Zones

Culture and Methodologies Agile Career Development Methodologies Team Management
Data Engineering AI/ML Big Data Data Databases IoT
Software Design and Architecture Cloud Architecture Containers Integration Microservices Performance Security
Coding Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks
Culture and Methodologies
Agile Career Development Methodologies Team Management
Data Engineering
AI/ML Big Data Data Databases IoT
Software Design and Architecture
Cloud Architecture Containers Integration Microservices Performance Security
Coding
Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance
Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks

The software you build is only as secure as the code that powers it. Learn how malicious code creeps into your software supply chain.

Apache Cassandra combines the benefits of major NoSQL databases to support data management needs not covered by traditional RDBMS vendors.

Generative AI has transformed nearly every industry. How can you leverage GenAI to improve your productivity and efficiency?

Modernize your data layer. Learn how to design cloud-native database architectures to meet the evolving demands of AI and GenAI workloads.

Related

  • Metal and the Simulated Annealing Algorithm
  • Reactive Kafka With Spring Boot
  • Why You Should Migrate Microservices From Java to Kotlin: Experience and Insights
  • How To Create a Homescreen Widget in Android

Trending

  • AI-Driven Test Automation Techniques for Multimodal Systems
  • The Role of AI in Identity and Access Management for Organizations
  • Mastering Advanced Traffic Management in Multi-Cloud Kubernetes: Scaling With Multiple Istio Ingress Gateways
  • Simplifying Multi-LLM Integration With KubeMQ
  1. DZone
  2. Coding
  3. Languages
  4. Cancelling Coroutines in Kotlin

Cancelling Coroutines in Kotlin

How to cancel a coroutine and how to write a coroutine that can actually be cancelled

By 
Dan Newton user avatar
Dan Newton
·
May. 12, 20 · Tutorial
Likes (1)
Comment
Save
Tweet
Share
11.2K Views

Join the DZone community and get the full member experience.

Join For Free

Sooner or later, you will need to cancel a coroutine that you started. Let’s look at how you can do just that.

How to Cancel a Job

One of the functions available to a job is cancel. I don’t know about you, but that sounds like the function we need. We can also leverage join to wait until the job is finished cancelling.

The example below shows a job being cancelled:

Kotlin
 
xxxxxxxxxx
1
14
 
1
runBlocking {
2
  val job = launch(Dispatchers.Default) {
3
    for (i in 0..1000) {
4
      delay(50)
5
      println("$i..")
6
    }
7
    println("Job is completed")
8
  }
9
  delay(500)
10
  println("Cancelling")
11
  job.cancel()
12
  job.join()
13
  println("Cancelled and done")
14
}


Outputting:

Plain Text
 
xxxxxxxxxx
1
11
 
1
0..
2
1..
3
2..
4
3..
5
4..
6
5..
7
6..
8
7..
9
8..
10
Cancelling
11
Cancelled and done


Note, that Job is completed is never output, as the job was cancelled before this could occur.

Job also provides cancelAndJoin to combine the two parts together. This will be used for the remainder of this post.

Cancellation Is Cooperative

As the Kotlin docs say, “coroutine cancellation is cooperative”. I really like this wording, and I think it goes a long way to describe what your part in writing coroutines that are cancellable.

I think this wording is so good, that I stole the heading from the Kotlin docs - Cancellation is cooperative… There is no better way to word what needs to be said here. If you want to leave and go through their docs instead, I understand.

For anyone that has decided to stay, let’s carry on.

A coroutine needs to cooperate to be cancellable. In other words, you need to take into account the contents of your coroutines to ensure that they can be cancelled. You can make your coroutines cancellable by following the two options below:

  • Calling any suspending functions from kotlinx.coroutines
  • Using CoroutineScope.isActive and handling the outcome appropriately

Both of these will be expanded in the following sections.

Kotlinx Suspending Functions Are Cancellable

All of the suspending functions provided by kotlinx.coroutines will check if the coroutine calling them is cancelled and throw a CancellationException if it has been.

The previous example demonstrated this. I have added it below again but tidied it up a little bit:

Kotlin
xxxxxxxxxx
1
10
 
1
runBlocking {
2
  val job = launch(Dispatchers.Default) {
3
    for (i in 0..1000) {
4
      delay(50)
5
      println("$i..")
6
    }
7
  }
8
  delay(500)
9
  job.cancelAndJoin()
10
}


delay is a suspending function and checks if the coroutine has been cancelled. The code for delay indicates how it handles cancellation:

Kotlin
xxxxxxxxxx
1
17
 
1
/**
2
 * Delays coroutine for a given time without blocking a thread and resumes it after a specified time.
3
 * This suspending function is cancellable.
4
 * If the [Job] of the current coroutine is cancelled or completed while this suspending function is waiting, this function
5
 * immediately resumes with [CancellationException].
6
 *
7
 * Note that delay can be used in [select] invocation with [onTimeout][SelectBuilder.onTimeout] clause.
8
 *
9
 * Implementation note: how exactly time is tracked is an implementation detail of [CoroutineDispatcher] in the context.
10
 * @param timeMillis time in milliseconds.
11
 */
12
public suspend fun delay(timeMillis: Long) {
13
    if (timeMillis <= 0) return // don't delay
14
    return suspendCancellableCoroutine sc@ { cont: CancellableContinuation<Unit> ->
15
        cont.context.delay.scheduleResumeAfterDelay(timeMillis, cont)
16
    }
17
}


The Kdoc mentions what happens if the calling coroutine has been cancelled. The same documentation is also included in other suspending functions. Furthermore, the call to suspendCancellableCoroutine provides a few more clues into what happens, but we’re not going to continue this investigation any further.

You might have also noticed that the CancellationException thrown by delay is not caught in the previous example. It is handled by the coroutine and will not propagate to the calling thread. You can decide to catch it inside your coroutine, but be aware that any calls to built-in suspending functions will end up throwing another CancellationException. Some extra information around this topic that is not covered here can be found in the Kotlin docs - Closing resources with finally.

Checking isActive

Cancelling a coroutine will change a job’s isActive flag to false. This flag can then be used to check if a job is still running, has been cancelled, or is in one of the other non-running states.

As discussed in the previous section, the built-in coroutines will handle cancellation for you. Therefore, you’re going to need to check the isActive flag for the following reasons:

  • You aren’t using any suspending functions from kotlinx-coroutines inside your coroutine.
  • You have parts of your coroutine that don’t call a suspending function.
  • You are writing your own suspending function that should be cancellable.

To explore the use of the isActive flag, we will focus on a set of examples consisting of coroutines that continually loop until they are cancelled.

Below are some methods you can use to handle cancellation:

  • Conditionally executing code using isActive as part of a while loop.
  • Conditionally executing code using isActive and an if statement.
  • Using return to escape.
  • Throwing an exception using isActive
  • Escaping a coroutine using ensureActive

The following sub-sections contain examples of these.

Conditionally Executing Code Using isActive as Part of a While Loop

isActive is a boolean property; therefore, it can be used in a while loop:

Kotlin
xxxxxxxxxx
1
10
 
1
runBlocking {
2
  val job = launch(Dispatchers.Default) {
3
    while (isActive) {
4
      Thread.sleep(50)
5
      println("I am still going..")
6
    }
7
  }
8
  delay(500)
9
  job.cancelAndJoin()
10
}


This example checks the isActive flag as part of the while loop. Once the job is cancelled, the value becomes false, and the loop ends.

Conditionally Executing Code Using isActive and an If Statement

In a very similar way to the while loop, you can check the state of isActive with an if statement to decide whether to execute some code:

Kotlin
xxxxxxxxxx
1
12
 
1
runBlocking {
2
  val job = launch(Dispatchers.Default) {
3
    for (it in 0..1000) {
4
      if (isActive) {
5
        Thread.sleep(50)
6
        println("I am still going..")
7
      }
8
    }
9
  }
10
  delay(500)
11
  job.cancelAndJoin()
12
}


Using Return to Escape

You can use return to break out of a loop and stop further processing if the job has been cancelled:

Kotlin
xxxxxxxxxx
1
13
 
1
runBlocking {
2
  val job = launch(Dispatchers.Default) {
3
    for (it in 0..1000) {
4
      if (!isActive) {
5
        return@launch
6
      }
7
      Thread.sleep(50)
8
      println("I am still going..")
9
    }
10
  }
11
  delay(500)
12
  job.cancelAndJoin()
13
}


Throwing an Exception Using isActive

This is similar to escaping using return, but changes it up a bit and throws an exception:

Kotlin
xxxxxxxxxx
1
13
 
1
runBlocking {
2
  val job = launch(Dispatchers.Default) {
3
    for (it in 0..1000) {
4
      if (!isActive) {
5
        throw CancellationException("I have been cancelled")
6
      }
7
      Thread.sleep(50)
8
      println("I am still going..")
9
    }
10
  }
11
  delay(500)
12
  job.cancelAndJoin()
13
}


Escaping a Coroutine Using ensureActive

ensureActive is a helper function that throws a CancellationException if the coroutine has been cancelled:

Kotlin
xxxxxxxxxx
1
11
 
1
runBlocking {
2
  val job = launch(Dispatchers.Default) {
3
    for (it in 0..1000) {
4
      ensureActive()
5
      Thread.sleep(50)
6
      println("I am still going..")
7
    }
8
  }
9
  delay(500)
10
  job.cancelAndJoin()
11
}


Calling ensureActive removes the need to call the earlier method that throws an exception. Furthermore, it has access to the exception passed into Job.cancel.

Summary

We have looked at how to cancel a job and how to write a coroutine that can be cancelled.

A job can be cancelled by calling cancel or cancelAndJoin.

It is important to remember that a coroutine must cooperate to be cancellable. You can make a coroutine cancellable by:

  • Calling any suspending functions from kotlinx.coroutines
  • Using CoroutineScope.isActive and handling the outcome appropriately.

It is highly likely that your coroutines will call at least one of the provided suspending functions. Doing so removes the need to explicitly make your coroutines cancellable. That being said, it is an important subject to be aware of when you do eventually write a coroutine that does not call any of the kotlinx.coroutines suspending functions.

If you enjoyed this post or found it helpful (or both) then please feel free to follow me on Twitter at @LankyDanDev and remember to share with anyone else who might find this useful!

Kotlin (programming language)

Published at DZone with permission of Dan Newton, DZone MVB. See the original article here.

Opinions expressed by DZone contributors are their own.

Related

  • Metal and the Simulated Annealing Algorithm
  • Reactive Kafka With Spring Boot
  • Why You Should Migrate Microservices From Java to Kotlin: Experience and Insights
  • How To Create a Homescreen Widget in Android

Partner Resources

×

Comments
Oops! Something Went Wrong

The likes didn't load as expected. Please refresh the page and try again.

ABOUT US

  • About DZone
  • Support and feedback
  • Community research
  • Sitemap

ADVERTISE

  • Advertise with DZone

CONTRIBUTE ON DZONE

  • Article Submission Guidelines
  • Become a Contributor
  • Core Program
  • Visit the Writers' Zone

LEGAL

  • Terms of Service
  • Privacy Policy

CONTACT US

  • 3343 Perimeter Hill Drive
  • Suite 100
  • Nashville, TN 37211
  • support@dzone.com

Let's be friends:

Likes
There are no likes...yet! 👀
Be the first to like this post!
It looks like you're not logged in.
Sign in to see who liked this post!