1<!--- TEST_NAME CancellationGuideTest --> 2 3**Table of contents** 4 5<!--- TOC --> 6 7* [Cancellation and Timeouts](#cancellation-and-timeouts) 8 * [Cancelling coroutine execution](#cancelling-coroutine-execution) 9 * [Cancellation is cooperative](#cancellation-is-cooperative) 10 * [Making computation code cancellable](#making-computation-code-cancellable) 11 * [Closing resources with `finally`](#closing-resources-with-finally) 12 * [Run non-cancellable block](#run-non-cancellable-block) 13 * [Timeout](#timeout) 14 * [Asynchronous timeout and resources](#asynchronous-timeout-and-resources) 15 16<!--- END --> 17 18## Cancellation and Timeouts 19 20This section covers coroutine cancellation and timeouts. 21 22### Cancelling coroutine execution 23 24In a long-running application you might need fine-grained control on your background coroutines. 25For example, a user might have closed the page that launched a coroutine and now its result 26is no longer needed and its operation can be cancelled. 27The [launch] function returns a [Job] that can be used to cancel the running coroutine: 28 29<div class="sample" markdown="1" theme="idea" data-min-compiler-version="1.3"> 30 31```kotlin 32import kotlinx.coroutines.* 33 34fun main() = runBlocking { 35//sampleStart 36 val job = launch { 37 repeat(1000) { i -> 38 println("job: I'm sleeping $i ...") 39 delay(500L) 40 } 41 } 42 delay(1300L) // delay a bit 43 println("main: I'm tired of waiting!") 44 job.cancel() // cancels the job 45 job.join() // waits for job's completion 46 println("main: Now I can quit.") 47//sampleEnd 48} 49``` 50 51</div> 52 53> You can get the full code [here](../kotlinx-coroutines-core/jvm/test/guide/example-cancel-01.kt). 54 55It produces the following output: 56 57```text 58job: I'm sleeping 0 ... 59job: I'm sleeping 1 ... 60job: I'm sleeping 2 ... 61main: I'm tired of waiting! 62main: Now I can quit. 63``` 64 65<!--- TEST --> 66 67As soon as main invokes `job.cancel`, we don't see any output from the other coroutine because it was cancelled. 68There is also a [Job] extension function [cancelAndJoin] 69that combines [cancel][Job.cancel] and [join][Job.join] invocations. 70 71### Cancellation is cooperative 72 73Coroutine cancellation is _cooperative_. A coroutine code has to cooperate to be cancellable. 74All the suspending functions in `kotlinx.coroutines` are _cancellable_. They check for cancellation of 75coroutine and throw [CancellationException] when cancelled. However, if a coroutine is working in 76a computation and does not check for cancellation, then it cannot be cancelled, like the following 77example shows: 78 79<div class="sample" markdown="1" theme="idea" data-min-compiler-version="1.3"> 80 81```kotlin 82import kotlinx.coroutines.* 83 84fun main() = runBlocking { 85//sampleStart 86 val startTime = System.currentTimeMillis() 87 val job = launch(Dispatchers.Default) { 88 var nextPrintTime = startTime 89 var i = 0 90 while (i < 5) { // computation loop, just wastes CPU 91 // print a message twice a second 92 if (System.currentTimeMillis() >= nextPrintTime) { 93 println("job: I'm sleeping ${i++} ...") 94 nextPrintTime += 500L 95 } 96 } 97 } 98 delay(1300L) // delay a bit 99 println("main: I'm tired of waiting!") 100 job.cancelAndJoin() // cancels the job and waits for its completion 101 println("main: Now I can quit.") 102//sampleEnd 103} 104``` 105 106</div> 107 108> You can get the full code [here](../kotlinx-coroutines-core/jvm/test/guide/example-cancel-02.kt). 109 110Run it to see that it continues to print "I'm sleeping" even after cancellation 111until the job completes by itself after five iterations. 112 113<!--- TEST 114job: I'm sleeping 0 ... 115job: I'm sleeping 1 ... 116job: I'm sleeping 2 ... 117main: I'm tired of waiting! 118job: I'm sleeping 3 ... 119job: I'm sleeping 4 ... 120main: Now I can quit. 121--> 122 123### Making computation code cancellable 124 125There are two approaches to making computation code cancellable. The first one is to periodically 126invoke a suspending function that checks for cancellation. There is a [yield] function that is a good choice for that purpose. 127The other one is to explicitly check the cancellation status. Let us try the latter approach. 128 129Replace `while (i < 5)` in the previous example with `while (isActive)` and rerun it. 130 131<div class="sample" markdown="1" theme="idea" data-min-compiler-version="1.3"> 132 133```kotlin 134import kotlinx.coroutines.* 135 136fun main() = runBlocking { 137//sampleStart 138 val startTime = System.currentTimeMillis() 139 val job = launch(Dispatchers.Default) { 140 var nextPrintTime = startTime 141 var i = 0 142 while (isActive) { // cancellable computation loop 143 // print a message twice a second 144 if (System.currentTimeMillis() >= nextPrintTime) { 145 println("job: I'm sleeping ${i++} ...") 146 nextPrintTime += 500L 147 } 148 } 149 } 150 delay(1300L) // delay a bit 151 println("main: I'm tired of waiting!") 152 job.cancelAndJoin() // cancels the job and waits for its completion 153 println("main: Now I can quit.") 154//sampleEnd 155} 156``` 157 158</div> 159 160> You can get the full code [here](../kotlinx-coroutines-core/jvm/test/guide/example-cancel-03.kt). 161 162As you can see, now this loop is cancelled. [isActive] is an extension property 163available inside the coroutine via the [CoroutineScope] object. 164 165<!--- TEST 166job: I'm sleeping 0 ... 167job: I'm sleeping 1 ... 168job: I'm sleeping 2 ... 169main: I'm tired of waiting! 170main: Now I can quit. 171--> 172 173### Closing resources with `finally` 174 175Cancellable suspending functions throw [CancellationException] on cancellation which can be handled in 176the usual way. For example, `try {...} finally {...}` expression and Kotlin `use` function execute their 177finalization actions normally when a coroutine is cancelled: 178 179 180<div class="sample" markdown="1" theme="idea" data-min-compiler-version="1.3"> 181 182```kotlin 183import kotlinx.coroutines.* 184 185fun main() = runBlocking { 186//sampleStart 187 val job = launch { 188 try { 189 repeat(1000) { i -> 190 println("job: I'm sleeping $i ...") 191 delay(500L) 192 } 193 } finally { 194 println("job: I'm running finally") 195 } 196 } 197 delay(1300L) // delay a bit 198 println("main: I'm tired of waiting!") 199 job.cancelAndJoin() // cancels the job and waits for its completion 200 println("main: Now I can quit.") 201//sampleEnd 202} 203``` 204 205</div> 206 207> You can get the full code [here](../kotlinx-coroutines-core/jvm/test/guide/example-cancel-04.kt). 208 209Both [join][Job.join] and [cancelAndJoin] wait for all finalization actions to complete, 210so the example above produces the following output: 211 212```text 213job: I'm sleeping 0 ... 214job: I'm sleeping 1 ... 215job: I'm sleeping 2 ... 216main: I'm tired of waiting! 217job: I'm running finally 218main: Now I can quit. 219``` 220 221<!--- TEST --> 222 223### Run non-cancellable block 224 225Any attempt to use a suspending function in the `finally` block of the previous example causes 226[CancellationException], because the coroutine running this code is cancelled. Usually, this is not a 227problem, since all well-behaving closing operations (closing a file, cancelling a job, or closing any kind of a 228communication channel) are usually non-blocking and do not involve any suspending functions. However, in the 229rare case when you need to suspend in a cancelled coroutine you can wrap the corresponding code in 230`withContext(NonCancellable) {...}` using [withContext] function and [NonCancellable] context as the following example shows: 231 232<div class="sample" markdown="1" theme="idea" data-min-compiler-version="1.3"> 233 234```kotlin 235import kotlinx.coroutines.* 236 237fun main() = runBlocking { 238//sampleStart 239 val job = launch { 240 try { 241 repeat(1000) { i -> 242 println("job: I'm sleeping $i ...") 243 delay(500L) 244 } 245 } finally { 246 withContext(NonCancellable) { 247 println("job: I'm running finally") 248 delay(1000L) 249 println("job: And I've just delayed for 1 sec because I'm non-cancellable") 250 } 251 } 252 } 253 delay(1300L) // delay a bit 254 println("main: I'm tired of waiting!") 255 job.cancelAndJoin() // cancels the job and waits for its completion 256 println("main: Now I can quit.") 257//sampleEnd 258} 259``` 260 261</div> 262 263> You can get the full code [here](../kotlinx-coroutines-core/jvm/test/guide/example-cancel-05.kt). 264 265<!--- TEST 266job: I'm sleeping 0 ... 267job: I'm sleeping 1 ... 268job: I'm sleeping 2 ... 269main: I'm tired of waiting! 270job: I'm running finally 271job: And I've just delayed for 1 sec because I'm non-cancellable 272main: Now I can quit. 273--> 274 275### Timeout 276 277The most obvious practical reason to cancel execution of a coroutine 278is because its execution time has exceeded some timeout. 279While you can manually track the reference to the corresponding [Job] and launch a separate coroutine to cancel 280the tracked one after delay, there is a ready to use [withTimeout] function that does it. 281Look at the following example: 282 283<div class="sample" markdown="1" theme="idea" data-min-compiler-version="1.3"> 284 285```kotlin 286import kotlinx.coroutines.* 287 288fun main() = runBlocking { 289//sampleStart 290 withTimeout(1300L) { 291 repeat(1000) { i -> 292 println("I'm sleeping $i ...") 293 delay(500L) 294 } 295 } 296//sampleEnd 297} 298``` 299 300</div> 301 302> You can get the full code [here](../kotlinx-coroutines-core/jvm/test/guide/example-cancel-06.kt). 303 304It produces the following output: 305 306```text 307I'm sleeping 0 ... 308I'm sleeping 1 ... 309I'm sleeping 2 ... 310Exception in thread "main" kotlinx.coroutines.TimeoutCancellationException: Timed out waiting for 1300 ms 311``` 312 313<!--- TEST STARTS_WITH --> 314 315The `TimeoutCancellationException` that is thrown by [withTimeout] is a subclass of [CancellationException]. 316We have not seen its stack trace printed on the console before. That is because 317inside a cancelled coroutine `CancellationException` is considered to be a normal reason for coroutine completion. 318However, in this example we have used `withTimeout` right inside the `main` function. 319 320Since cancellation is just an exception, all resources are closed in the usual way. 321You can wrap the code with timeout in a `try {...} catch (e: TimeoutCancellationException) {...}` block if 322you need to do some additional action specifically on any kind of timeout or use the [withTimeoutOrNull] function 323that is similar to [withTimeout] but returns `null` on timeout instead of throwing an exception: 324 325<div class="sample" markdown="1" theme="idea" data-min-compiler-version="1.3"> 326 327```kotlin 328import kotlinx.coroutines.* 329 330fun main() = runBlocking { 331//sampleStart 332 val result = withTimeoutOrNull(1300L) { 333 repeat(1000) { i -> 334 println("I'm sleeping $i ...") 335 delay(500L) 336 } 337 "Done" // will get cancelled before it produces this result 338 } 339 println("Result is $result") 340//sampleEnd 341} 342``` 343 344</div> 345 346> You can get the full code [here](../kotlinx-coroutines-core/jvm/test/guide/example-cancel-07.kt). 347 348There is no longer an exception when running this code: 349 350```text 351I'm sleeping 0 ... 352I'm sleeping 1 ... 353I'm sleeping 2 ... 354Result is null 355``` 356 357<!--- TEST --> 358 359### Asynchronous timeout and resources 360 361<!-- 362 NOTE: Don't change this section name. It is being referenced to from within KDoc of withTimeout functions. 363--> 364 365The timeout event in [withTimeout] is asynchronous with respect to the code running in its block and may happen at any time, 366even right before the return from inside of the timeout block. Keep this in mind if you open or acquire some 367resource inside the block that needs closing or release outside of the block. 368 369For example, here we imitate a closeable resource with the `Resource` class, that simply keeps track of how many times 370it was created by incrementing the `acquired` counter and decrementing this counter from its `close` function. 371Let us run a lot of coroutines with the small timeout try acquire this resource from inside 372of the `withTimeout` block after a bit of delay and release it from outside. 373 374<div class="sample" markdown="1" theme="idea" data-min-compiler-version="1.3"> 375 376```kotlin 377import kotlinx.coroutines.* 378 379//sampleStart 380var acquired = 0 381 382class Resource { 383 init { acquired++ } // Acquire the resource 384 fun close() { acquired-- } // Release the resource 385} 386 387fun main() { 388 runBlocking { 389 repeat(100_000) { // Launch 100K coroutines 390 launch { 391 val resource = withTimeout(60) { // Timeout of 60 ms 392 delay(50) // Delay for 50 ms 393 Resource() // Acquire a resource and return it from withTimeout block 394 } 395 resource.close() // Release the resource 396 } 397 } 398 } 399 // Outside of runBlocking all coroutines have completed 400 println(acquired) // Print the number of resources still acquired 401} 402//sampleEnd 403``` 404 405</div> 406 407> You can get the full code [here](../kotlinx-coroutines-core/jvm/test/guide/example-cancel-08.kt). 408 409<!--- CLEAR --> 410 411If you run the above code you'll see that it does not always print zero, though it may depend on the timings 412of your machine you may need to tweak timeouts in this example to actually see non-zero values. 413 414> Note, that incrementing and decrementing `acquired` counter here from 100K coroutines is completely safe, 415> since it always happens from the same main thread. More on that will be explained in the next chapter 416> on coroutine context. 417 418To workaround this problem you can store a reference to the resource in the variable as opposed to returning it 419from the `withTimeout` block. 420 421<div class="sample" markdown="1" theme="idea" data-min-compiler-version="1.3"> 422 423```kotlin 424import kotlinx.coroutines.* 425 426var acquired = 0 427 428class Resource { 429 init { acquired++ } // Acquire the resource 430 fun close() { acquired-- } // Release the resource 431} 432 433fun main() { 434//sampleStart 435 runBlocking { 436 repeat(100_000) { // Launch 100K coroutines 437 launch { 438 var resource: Resource? = null // Not acquired yet 439 try { 440 withTimeout(60) { // Timeout of 60 ms 441 delay(50) // Delay for 50 ms 442 resource = Resource() // Store a resource to the variable if acquired 443 } 444 // We can do something else with the resource here 445 } finally { 446 resource?.close() // Release the resource if it was acquired 447 } 448 } 449 } 450 } 451 // Outside of runBlocking all coroutines have completed 452 println(acquired) // Print the number of resources still acquired 453//sampleEnd 454} 455``` 456 457</div> 458 459> You can get the full code [here](../kotlinx-coroutines-core/jvm/test/guide/example-cancel-09.kt). 460 461This example always prints zero. Resources do not leak. 462 463<!--- TEST 4640 465--> 466 467<!--- MODULE kotlinx-coroutines-core --> 468<!--- INDEX kotlinx.coroutines --> 469[launch]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/launch.html 470[Job]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-job/index.html 471[cancelAndJoin]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/cancel-and-join.html 472[Job.cancel]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-job/cancel.html 473[Job.join]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-job/join.html 474[CancellationException]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-cancellation-exception/index.html 475[yield]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/yield.html 476[isActive]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/is-active.html 477[CoroutineScope]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-coroutine-scope/index.html 478[withContext]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/with-context.html 479[NonCancellable]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-non-cancellable.html 480[withTimeout]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/with-timeout.html 481[withTimeoutOrNull]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/with-timeout-or-null.html 482<!--- END --> 483