— Kotlin, Coroutines, Cancellation — 2 min read
Kotlin coroutines are a great tool for writing asynchronous, non-blocking code in a more readable and concise way. They allow you to write code that looks like it's running synchronously, but actually runs asynchronously in the background, and can be canceled at any time.
There are several ways to cancel a Kotlin coroutine, depending on the context in which it is running and the desired behavior. In this article, we will explore some of the most common ways to cancel a coroutine and discuss the pros and cons of each approach.
cancel
The most straightforward way to cancel a Kotlin coroutine is to use the cancel
function. This function is a member of the Job
interface, which is implemented by all coroutines. To cancel a coroutine, you simply need to call cancel
on the Job
object that represents the coroutine.
Here's an example of how you might use cancel
to cancel a coroutine:
1import kotlinx.coroutines.*2
3fun main() {4 val job = GlobalScope.launch {5 // do some work6 }7 // cancel the coroutine8 job.cancel()9}
Calling cancel
will immediately stop the coroutine and prevent it from executing any further. However, it's important to note that cancel
only works if the coroutine is in a cancellable state. Some blocking operations, such as delay
and withContext
, are cancellable, while others, such as sleep
and blocking
, are not.
If you try to cancel a coroutine that is not in a cancellable state, the cancel
function will return false
to indicate that the coroutine could not be canceled.
Another way to cancel a Kotlin coroutine is to throw a CancellationException
. This is a special exception that is thrown when a coroutine is canceled, and it can be caught and handled like any other exception.
Here's an example of how you might use a CancellationException
to cancel a coroutine:
1import kotlinx.coroutines.*2
3fun main() {4 val job = GlobalScope.launch {5 try {6 // do some work7 } catch (e: CancellationException) {8 // handle the cancellation9 }10 }11 // cancel the coroutine12 job.cancel()13}
By catching the CancellationException
, you can perform any necessary cleanup or wrap up any unfinished work before the coroutine is terminated.
Sometimes, you may want to cancel a coroutine if it takes too long to complete. In this case, you can use the withTimeout
function to specify a maximum duration for the coroutine to run. If the coroutine takes longer than the specified timeout, it will be canceled and a TimeoutCancellationException
will be thrown.
Here's an example of how you might use a timeout to cancel a coroutine:
1import kotlinx.coroutines.*2
3fun main() {4 val job = GlobalScope.launch {5 try {6 withTimeout(1000) {7 // do some work8 }9 } catch (e: TimeoutCancellationException) {10 // handle the cancellation11 }12 }13 // cancel the coroutine after the specified timeout14 job.cancel()15}
In this example, the coroutine will be canceled after 1000 milliseconds (1 second) if it has not already completed. If the coroutine takes longer than the specified timeout, a TimeoutCancellationException
will be thrown and can be caught and handled in the usual way.
Kotlin coroutines support a concept called "job hierarchy," which allows you to create a parent-child relationship between coroutines. If a parent coroutine is canceled, all of its child coroutines will also be canceled. This can be useful for organizing and managing complex coroutine structures.
Here's an example of how you might use a job hierarchy to cancel a coroutine:
1import kotlinx.coroutines.*2
3fun main() {4 val parentJob = Job()5 val childJob = CoroutineScope(parentJob).launch {6 // do some work7 }8 // cancel the parent job and all child coroutines9 parentJob.cancel()10}
In this example, canceling the parentJob
will also cancel the childJob
coroutine, as well as any other child coroutines that may be running under the parentJob
.
In this article, we looked at several ways to cancel a Kotlin coroutine, including using the cancel
function, throwing a CancellationException
, using a timeout with withTimeout
, and using a job hierarchy with parent-child relationships. Each approach has its own pros and cons, and the best choice will depend on your specific needs and use case. I wrote another article recently about how to run some code with a delay using LaunchedEffect in Jetpack Compose so if you're interested in Compose feel free to check it out!