๐ŸŽ“ A1Learner
๐Ÿ”
0 of 28 lessons completed

Kotlin Cancellation

Cancellation stops a coroutine through its Job in a cooperative way. Coroutine code notices the stop at suspension points such as delay.

Cancelling a delay loop

The parent waits 120 first, so the child is inside its loop when cancel is called. The child catches CancellationException and prints its line.

import kotlinx.coroutines.*

fun main() = runBlocking {
    val job = launch {
        try {
            repeat(100) {
                delay(50)
            }
            println("finished normally")
        } catch (e: CancellationException) {
            println("cancelled cleanly")
        }
    }
    delay(120)
    job.cancel()
    job.join()
    println("isCancelled: ${job.isCancelled}")
}

A final line and a cleanup line

The catch block prints when the long delay is cancelled. The finally block prints after it, and join then confirms the job has fully stopped.

import kotlinx.coroutines.*

fun main() = runBlocking {
    val job = launch {
        try {
            delay(1000)
            println("long work done")
        } catch (e: CancellationException) {
            println("stopped early")
        } finally {
            println("cleanup done")
        }
    }
    delay(60)
    job.cancel()
    job.join()
    println(job.isActive)
    println(job.isCompleted)
}

Cancelling a finished job

This job ends in the normal way before cancel is called. The late cancel has no work to stop, so isCancelled still reads false.

import kotlinx.coroutines.*

fun main() = runBlocking {
    val job = launch {
        println("quick done")
    }
    job.join()
    job.cancel()
    println(job.isCancelled)
}

withTimeout stops a long delay

The limit of 50 runs out long before the delay of 5000 ends. The catch block prints timed out, and the code after it still runs.

import kotlinx.coroutines.*

fun main() = runBlocking {
    try {
        withTimeout(50) {
            delay(5000)
            println("never printed")
        }
    } catch (e: TimeoutCancellationException) {
        println("timed out")
    }
    println("after")
}

withTimeout that finishes in time

This block needs only a short delay, well inside its limit. withTimeout returns the block value as usual.

import kotlinx.coroutines.*

fun main() = runBlocking {
    val result = withTimeout(1000) {
        delay(10)
        "in time"
    }
    println(result)
}

Example

import kotlinx.coroutines.*

fun main() = runBlocking {
    val job = launch {
        try {
            repeat(100) {
                delay(50)
            }
            println("loop finished")
        } catch (e: CancellationException) {
            println("loop stopped")
        }
    }
    delay(110)
    job.cancel()
    job.join()
    println("cancelled: ${job.isCancelled}")
    try {
        withTimeout(40) {
            delay(1000)
        }
    } catch (e: TimeoutCancellationException) {
        println("timed out")
    }
    println("main continues")
}

Key points

  • The cancel call on a job asks its coroutine to stop.
  • Suspending calls such as delay check for cancellation as they wait.
  • That check throws CancellationException inside the coroutine.
  • Code can catch CancellationException to print a final line before the coroutine ends.
  • join after cancel waits until the coroutine has fully stopped.
  • After that wait, isCancelled reads true.
  • withTimeout cancels its block when the time limit runs out, and that throw is a TimeoutCancellationException.

In this example, the parent waits 110 so the loop is running before cancel is called. The loop catches CancellationException and prints loop stopped, and join leaves isCancelled true. The withTimeout block then times out on its long delay, and main still reaches its last line.

Note

A job must have started before a cancel call can stop running code.

Try it Yourself

Edit the code, then press Run.

Running is simulated ยท Kotlin Playground

Exercise

What happens when withTimeout(50) wraps a delay(5000) call?