0Pricing
Kotlin Academy · Lesson

Cooperative Cancellation: isActive and ensureActive

Make coroutines cancellable by checking isActive and calling ensureActive.

Cooperative Cancellation: isActive and ensureActive is a free Kotlin Academy lesson on CoddyKit — lesson 1 of 4. You can read the complete lesson below for free — then practise it hands-on in the browser with a built-in code editor and a 24/7 AI tutor. It is part of the Kotlin Academy learning path, one of 4 lessons in the course, and your progress syncs across the web and the CoddyKit app.

Why Cooperative Cancellation?

Kotlin coroutines use cooperative cancellation: a cancelled coroutine is only stopped when it cooperates by checking its cancellation state. A tight CPU loop will not stop until it checks.

import kotlinx.coroutines.*
fun main() = runBlocking {
    val job = launch {
        var i = 0
        while (true) { i++ } // never checks — never cancels!
    }
    delay(100)
    job.cancel()
    println("cancel sent but loop kept running until process ended")
}

Checking isActive

isActive is a property on CoroutineScope that returns false once cancellation is requested. Checking it in a loop makes the coroutine cooperate.

import kotlinx.coroutines.*
fun main() = runBlocking {
    val job = launch {
        var i = 0
        while (isActive) {
            i++
        }
        println("Loop exited after $i iterations")
    }
    delay(10)
    job.cancel()
    job.join()
}

ensureActive()

ensureActive() throws CancellationException if the coroutine is cancelled. It is a drop-in replacement for if (!isActive) throw CancellationException().

import kotlinx.coroutines.*
fun main() = runBlocking {
    val job = launch {
        try {
            repeat(1_000_000) {
                ensureActive()  // throws if cancelled
                // heavy computation here
            }
        } catch (e: CancellationException) {
            println("Cancelled at iteration")
            throw e
        }
    }
    delay(5)
    job.cancel()
    job.join()
}

yield() as a Cooperative Point

yield() suspends the coroutine briefly, allowing the scheduler to check cancellation and run other coroutines.

import kotlinx.coroutines.*
fun main() = runBlocking {
    val job = launch {
        repeat(10) { i ->
            yield()  // cooperative cancellation point + gives up scheduler
            println("Step $i")
        }
    }
    delay(2)
    job.cancel()
    job.join()
    println("Done")
}

isActive vs ensureActive

Use isActive for conditional logic (e.g., break out of a loop gracefully). Use ensureActive() when you want to propagate cancellation as an exception immediately.

import kotlinx.coroutines.*
fun main() = runBlocking {
    launch {
        repeat(100) { i ->
            if (!isActive) {
                println("Stopping at $i, cleaning up...")
                return@launch
            }
            // work
        }
    }.also { delay(5); it.cancel(); it.join() }
}

CancellationException is Special

CancellationException is not propagated to the parent — it signals normal cancellation. Never swallow it; always rethrow or let it propagate.

import kotlinx.coroutines.*
fun main() = runBlocking {
    val job = launch {
        try {
            delay(1000)
        } catch (e: CancellationException) {
            println("Caught cancellation, rethrowing")
            throw e  // important!
        }
    }
    delay(50)
    job.cancel()
    job.join()
    println("Job state: ${job.isCancelled}")
}

Cancellation in CPU-Bound Work

For CPU-bound work, insert ensureActive() or yield() at natural checkpoints to allow timely cancellation.

import kotlinx.coroutines.*
suspend fun heavyCompute(n: Int): Long {
    var result = 0L
    for (i in 0..n) {
        ensureActive()
        result += i
    }
    return result
}
fun main() = runBlocking {
    val job = launch(Dispatchers.Default) {
        println(heavyCompute(1_000_000))
    }
    delay(10)
    job.cancel()
    job.join()
}

isActive in a Custom Suspend Function

You can access isActive inside any suspend function via coroutineContext.isActive or by using currentCoroutineContext().isActive.

import kotlinx.coroutines.*
suspend fun checkable() {
    while (currentCoroutineContext().isActive) {
        delay(10)
        println("tick")
    }
}
fun main() = runBlocking {
    val job = launch { checkable() }
    delay(35)
    job.cancel()
    job.join()
}

Cancellation with Resource Cleanup

Even with cooperative cancellation, resources can be cleaned up in finally blocks — cancellation exception still propagates through them.

import kotlinx.coroutines.*
fun main() = runBlocking {
    val job = launch {
        try {
            repeat(100) { i ->
                ensureActive()
                delay(20)
                println("Working $i")
            }
        } finally {
            println("Cleanup done")
        }
    }
    delay(50)
    job.cancel()
    job.join()
}

Polling with isActive

Polling loops are a perfect fit for isActive: keep polling until the coroutine is cancelled.

import kotlinx.coroutines.*
fun main() = runBlocking {
    val job = launch {
        while (isActive) {
            println("Polling...")
            delay(100)
        }
        println("Poller stopped")
    }
    delay(350)
    job.cancel()
    job.join()
}

Difference from Thread.interrupted()

Unlike Java threads, Kotlin coroutines do not rely on interrupt flags. Cancellation is modeled via exceptions and scope state, making it composable and structured.

import kotlinx.coroutines.*
fun main() = runBlocking {
    // Kotlin way: cooperative via isActive / ensureActive
    launch {
        ensureActive()
        println("Kotlin coroutine: cancellation is explicit and structured")
    }
}

Quick Check

Which function throws immediately when a coroutine is cancelled?

Recap

Cooperative cancellation requires explicit check points: isActive for conditional exit, ensureActive() to throw immediately, yield() to suspend and give up the thread. Never swallow CancellationException.

Frequently asked questions

Is the “Cooperative Cancellation: isActive and ensureActive” lesson free?

Yes — the full text of “Cooperative Cancellation: isActive and ensureActive” is free to read here on the web, and the Kotlin Academy course includes 4 lessons in total. To practise it interactively (a built-in code editor and a 24/7 AI tutor) and unlock the rest of the Kotlin Academy course, upgrade to CoddyKit PRO.

What will I learn in “Cooperative Cancellation: isActive and ensureActive”?

Make coroutines cancellable by checking isActive and calling ensureActive. You practise Kotlin Academy with hands-on code you run directly in the browser, and a 24/7 AI tutor answers your questions as you work through the lesson.

Do I need any experience to start Kotlin Academy?

No prior experience is required. Kotlin Academy on CoddyKit is structured for beginners through advanced learners; this is — lesson 1 of 4, so you can start here or from the beginning and move at your own pace.

How long does the “Cooperative Cancellation: isActive and ensureActive” lesson take?

Most CoddyKit lessons take about 5–10 minutes. Each one is bite-sized and interactive, so you make steady progress and pick up exactly where you left off across the web and the app.

Can I write and run code in this Kotlin Academy lesson?

Yes. Every Kotlin Academy lesson includes a built-in code editor, so you write and run real code right in your browser and get instant AI feedback — no local setup required.

All lessons in this course

  1. Cooperative Cancellation: isActive and ensureActive
  2. withTimeout and withTimeoutOrNull
  3. Cleanup with finally and NonCancellable
  4. Cancellation Propagation in Coroutine Hierarchies
← Back to Kotlin Academy