Kotlin Coroutine Cancellation, Explained
Sam Cooper on 2025-04-16
Kotlin Coroutine Cancellation, Explained
Safely stop suspending tasks for faster apps and fewer bugs
Don’t let abandoned coroutines waste resources and tank your app’s performance. Keep things snappy by cancelling unneeded tasks as soon as you’re done with them. Let’s break down the how, why, and what of coroutine cancellation, so you can stop suspending tasks cleanly and handle cancellation exceptions with ease.
🤔 Why do coroutines need cancelling?
- Background tasks like coroutines can keep running — and using memory, CPU, and more — after their original caller goes away 👋.
- With async calls and loops in the mix ⏳🔁, a task could take a long time to finish, or even be designed intentionally to run forever.
- If you keep starting new coroutines faster than you stop them, your app will eventually run out of resources and slow down or crash 📉💥.
- Easiest way to stop a coroutine :
cancel()
its scope. Many built-in scopes, like Android’sviewModelScope
, will do this automatically. - When a scope is cancelled, all its existing coroutines are cancelled, and new ones can no longer be started. ☠️
- For fine-grained control, you can also manually
cancel()
an individual coroutine’sJob
orDeferred
.
- Make a coroutine scope for each app component that uses background tasks, and
cancel()
the scope when you exit that part of the app. 🏗️ - Even better, start your coroutines as children of an existing task, or inside a
coroutineScope()
block, so they inherit their parent’s lifecycle. - Some tasks really should keep running for the entire lifetime of the app. Those ones don’t need cancelling 💅.
- To release locks + resources and leave data in a valid state, running code must shut itself down—it can’t just be terminated abruptly from outside.
- When cancelled mid-wait 😴, a suspended coroutine wakes up, so it can run
finally
blocks and cleanup code before ending its execution. - If a coroutine goes a while without suspending, it should check to see whether it’s been asked to cancel itself—
ensureActive()
will work.
- To trigger a quick exit and avoid returning a real result, a cancelled suspension point resumes by throwing a
CancellationException
. - A coroutine that ends with an uncaught
CancellationException
will always terminate silently—⚠️ usually what you want, but not always! - The exception is thrown to get the coroutine to stop running quickly, but it’s not required — a cancelled task can also just
return
normally.
- You can’t un-cancel a coroutine by catching a
CancellationException
. TheJob
still knows it’s cancelled, and all future suspension points fail. - If a coroutine traps a
CancellationException
when trying to catch real errors, it might fail to exit promptly—or at all 🧟—when cancelled. - If you catch an unknown
Throwable
from a suspending function, check whether it’s a cancellation exception, and callensureActive()
if so ✅.
Kotlin Coroutine Confidence: Untangle Your Async, Ship Safety at Speed Escape callback hell and ship fast, clean code that reads as smoothly as it runs. Squash bugs and stamp out memory leaks with an intuitive concurrency toolkit. pragprog.com