In Kotlin, you cancel a coroutine safely by calling cancel() on its Job or scope, and making sure the coroutine code is cooperative: it should suspend regularly or check for cancellation.
Basic example
import kotlinx.coroutines.*
fun main() = runBlocking {
val job = launch {
try {
repeat(1_000) { i ->
println("Working $i")
delay(500) // cancellable suspension point
}
} finally {
println("Cleaning up")
}
}
delay(1_500)
job.cancel()
job.join()
println("Cancelled safely")
}
Or more commonly:
job.cancelAndJoin()
job.cancelAndJoin()
This cancels the coroutine and waits until it finishes cleanup.
Use cancellable suspending functions
Most kotlinx.coroutines suspending functions are cancellable, for example:
delay(...)
withContext(...)
receive(...)
send(...)
So this is usually safe:
val job = scope.launch {
while (true) {
delay(1000)
doWork()
}
}
job.cancel()
For CPU-heavy loops, check cancellation manually
If your coroutine does not suspend often, cancellation will not be noticed immediately.
Use isActive:
val job = scope.launch {
while (isActive) {
doCpuWorkChunk()
}
}
Or call ensureActive():
val job = scope.launch {
while (true) {
ensureActive()
doCpuWorkChunk()
}
}
Cleanup with finally
Use try/finally for cleanup:
val job = scope.launch {
try {
doWork()
} finally {
closeResources()
}
}
If cleanup needs to call suspending functions, use NonCancellable:
val job = scope.launch {
try {
doWork()
} finally {
withContext(NonCancellable) {
saveState()
closeRemoteConnection()
}
}
}
Do not swallow CancellationException
Cancellation is represented by CancellationException. Avoid catching it accidentally and ignoring it.
Bad:
try {
doWork()
} catch (e: Exception) {
// This also catches CancellationException
logError(e)
}
Better:
try {
doWork()
} catch (e: CancellationException) {
throw e
} catch (e: Exception) {
logError(e)
}
Cancel a scope
If you created a scope, cancel it when the owner is destroyed:
class Repository {
private val scope = CoroutineScope(SupervisorJob() + Dispatchers.IO)
fun start() {
scope.launch {
doWork()
}
}
fun close() {
scope.cancel()
}
}
Summary
Use:
job.cancelAndJoin()
Make your coroutine cooperative by using:
delay(...)
isActive
ensureActive()
Clean up with:
try {
// work
} finally {
// cleanup
}
And avoid swallowing CancellationException.
