1# Guide to UI programming with coroutines 2 3This guide assumes familiarity with basic coroutine concepts that are 4covered in [Guide to kotlinx.coroutines](../docs/topics/coroutines-guide.md) and gives specific 5examples on how to use coroutines in UI applications. 6 7All UI application libraries have one thing in common. They have the single main thread where all state of the UI 8is confined, and all updates to the UI has to happen in this particular thread. With respect to coroutines, 9it means that you need an appropriate _coroutine dispatcher context_ that confines the coroutine 10execution to this main UI thread. 11 12In particular, `kotlinx.coroutines` has three modules that provide coroutine context for 13different UI application libraries: 14 15* [kotlinx-coroutines-android](kotlinx-coroutines-android) -- `Dispatchers.Main` context for Android applications. 16* [kotlinx-coroutines-javafx](kotlinx-coroutines-javafx) -- `Dispatchers.JavaFx` context for JavaFX UI applications. 17* [kotlinx-coroutines-swing](kotlinx-coroutines-swing) -- `Dispatchers.Swing` context for Swing UI applications. 18 19Also, UI dispatcher is available via `Dispatchers.Main` from `kotlinx-coroutines-core` and corresponding 20implementation (Android, JavaFx or Swing) is discovered by [`ServiceLoader`](https://docs.oracle.com/javase/8/docs/api/java/util/ServiceLoader.html) API. 21For example, if you are writing JavaFx application, you can use either `Dispatchers.Main` or `Dispachers.JavaFx` extension, it will be the same object. 22 23This guide covers all UI libraries simultaneously, because each of these modules consists of just one 24object definition that is a couple of pages long. You can use any of them as an example to write the corresponding 25context object for your favourite UI library, even if it is not included out of the box here. 26 27## Table of contents 28 29<!--- TOC --> 30 31* [Setup](#setup) 32 * [JavaFx](#javafx) 33 * [Android](#android) 34* [Basic UI coroutines](#basic-ui-coroutines) 35 * [Launch UI coroutine](#launch-ui-coroutine) 36 * [Cancel UI coroutine](#cancel-ui-coroutine) 37* [Using actors within UI context](#using-actors-within-ui-context) 38 * [Extensions for coroutines](#extensions-for-coroutines) 39 * [At most one concurrent job](#at-most-one-concurrent-job) 40 * [Event conflation](#event-conflation) 41* [Blocking operations](#blocking-operations) 42 * [The problem of UI freezes](#the-problem-of-ui-freezes) 43 * [Structured concurrency, lifecycle and coroutine parent-child hierarchy](#structured-concurrency-lifecycle-and-coroutine-parent-child-hierarchy) 44 * [Blocking operations](#blocking-operations) 45* [Advanced topics](#advanced-topics) 46 * [Starting coroutine in UI event handlers without dispatch](#starting-coroutine-in-ui-event-handlers-without-dispatch) 47 48<!--- END --> 49 50## Setup 51 52The runnable examples in this guide are presented for JavaFx. The advantage is that all the examples can 53be directly started on any OS without the need for emulators or anything like that and they are fully self-contained 54(each example is in one file). 55There are separate notes on what changes need to be made (if any) to reproduce them on Android. 56 57### JavaFx 58 59The basic example application for JavaFx consists of a window with a text label named `hello` that initially 60contains "Hello World!" string and a pinkish circle in the bottom-right corner named `fab` (floating action button). 61 62 63 64The `start` function of JavaFX application invokes `setup` function, passing it reference to `hello` and `fab` 65nodes. That is where various code is placed in the rest of this guide: 66 67```kotlin 68fun setup(hello: Text, fab: Circle) { 69 // placeholder 70} 71``` 72 73> You can get the full code [here](kotlinx-coroutines-javafx/test/guide/example-ui-basic-01.kt). 74 75You can clone [kotlinx.coroutines](https://github.com/Kotlin/kotlinx.coroutines) project from GitHub onto your 76workstation and open the project in IDE. All the examples from this guide are in the test folder of 77[`ui/kotlinx-coroutines-javafx`](kotlinx-coroutines-javafx) module. 78This way you'll be able to run and see how each example works and to 79experiment with them by making changes. 80 81### Android 82 83Follow the guide on [Getting Started With Android and Kotlin](https://kotlinlang.org/docs/tutorials/kotlin-android.html) 84to create Kotlin project in Android Studio. You are also encouraged to add 85[Kotlin Android Extensions](https://kotlinlang.org/docs/tutorials/android-plugin.html) 86to your application. 87 88In Android Studio 2.3 you'll get an application that looks similarly to the one shown below: 89 90 91 92Go to the `context_main.xml` of your application and assign an ID of "hello" to the text view with "Hello World!" string, 93so that it is available in your application as `hello` with Kotlin Android extensions. The pinkish floating 94action button is already named `fab` in the project template that gets created. 95 96In the `MainActivity.kt` of your application remove the block `fab.setOnClickListener { ... }` and instead 97add `setup(hello, fab)` invocation as the last line of `onCreate` function. 98Create a placeholder `setup` function at the end of the file. 99That is where various code is placed in the rest of this guide: 100 101```kotlin 102fun setup(hello: TextView, fab: FloatingActionButton) { 103 // placeholder 104} 105``` 106 107<!--- CLEAR --> 108 109Add dependencies on `kotlinx-coroutines-android` module to the `dependencies { ... }` section of 110`app/build.gradle` file: 111 112```groovy 113implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.8.1" 114``` 115 116You can clone [kotlinx.coroutines](https://github.com/Kotlin/kotlinx.coroutines) project from GitHub onto your 117workstation. The resulting template project for Android resides in 118[`ui/kotlinx-coroutines-android/example-app`](kotlinx-coroutines-android/example-app) directory. 119You can load it in Android Studio to follow this guide on Android. 120 121## Basic UI coroutines 122 123This section shows basic usage of coroutines in UI applications. 124 125### Launch UI coroutine 126 127The `kotlinx-coroutines-javafx` module contains 128[Dispatchers.JavaFx][kotlinx.coroutines.Dispatchers.JavaFx] 129dispatcher that dispatches coroutine execution to 130the JavaFx application thread. We import it as `Main` to make all the presented examples 131easily portable to Android: 132 133```kotlin 134import kotlinx.coroutines.javafx.JavaFx as Main 135``` 136 137<!--- CLEAR --> 138 139Coroutines confined to the main UI thread can freely update anything in UI and suspend without blocking the main thread. 140For example, we can perform animations by coding them in imperative style. The following code updates the 141text with a 10 to 1 countdown twice a second, using [launch] coroutine builder: 142 143```kotlin 144fun setup(hello: Text, fab: Circle) { 145 GlobalScope.launch(Dispatchers.Main) { // launch coroutine in the main thread 146 for (i in 10 downTo 1) { // countdown from 10 to 1 147 hello.text = "Countdown $i ..." // update text 148 delay(500) // wait half a second 149 } 150 hello.text = "Done!" 151 } 152} 153``` 154 155> You can get the full code [here](kotlinx-coroutines-javafx/test/guide/example-ui-basic-02.kt). 156 157So, what happens here? Because we are launching coroutine in the main UI context, we can freely update UI from 158inside this coroutine and invoke _suspending functions_ like [delay] at the same time. UI is not frozen 159while `delay` waits, because it does not block the UI thread -- it just suspends the coroutine. 160 161> The corresponding code for Android application is the same. 162 You just need to copy the body of `setup` function into the corresponding function of Android project. 163 164### Cancel UI coroutine 165 166We can keep a reference to the [Job] object that `launch` function returns and use it to cancel 167coroutine when we want to stop it. Let us cancel the coroutine when pinkish circle is clicked: 168 169```kotlin 170fun setup(hello: Text, fab: Circle) { 171 val job = GlobalScope.launch(Dispatchers.Main) { // launch coroutine in the main thread 172 for (i in 10 downTo 1) { // countdown from 10 to 1 173 hello.text = "Countdown $i ..." // update text 174 delay(500) // wait half a second 175 } 176 hello.text = "Done!" 177 } 178 fab.onMouseClicked = EventHandler { job.cancel() } // cancel coroutine on click 179} 180``` 181 182> You can get the full code [here](kotlinx-coroutines-javafx/test/guide/example-ui-basic-03.kt). 183 184Now, if the circle is clicked while countdown is still running, the countdown stops. 185Note that [Job.cancel] is completely thread-safe and non-blocking. It just signals the coroutine to cancel 186its job, without waiting for it to actually terminate. It can be invoked from anywhere. 187Invoking it on a coroutine that was already cancelled or has completed does nothing. 188 189> The corresponding line for Android is shown below: 190 191```kotlin 192fab.setOnClickListener { job.cancel() } // cancel coroutine on click 193``` 194 195<!--- CLEAR --> 196 197## Using actors within UI context 198 199In this section we show how UI applications can use actors within their UI context make sure that 200there is no unbounded growth in the number of launched coroutines. 201 202### Extensions for coroutines 203 204Our goal is to write an extension _coroutine builder_ function named `onClick`, 205so that we can perform countdown animation every time when the circle is clicked with this simple code: 206 207```kotlin 208fun setup(hello: Text, fab: Circle) { 209 fab.onClick { // start coroutine when the circle is clicked 210 for (i in 10 downTo 1) { // countdown from 10 to 1 211 hello.text = "Countdown $i ..." // update text 212 delay(500) // wait half a second 213 } 214 hello.text = "Done!" 215 } 216} 217``` 218 219<!--- INCLUDE .*/example-ui-actor-([0-9]+).kt --> 220 221Our first implementation for `onClick` just launches a new coroutine on each mouse event and 222passes the corresponding mouse event into the supplied action (just in case we need it): 223 224```kotlin 225fun Node.onClick(action: suspend (MouseEvent) -> Unit) { 226 onMouseClicked = EventHandler { event -> 227 GlobalScope.launch(Dispatchers.Main) { 228 action(event) 229 } 230 } 231} 232``` 233 234> You can get the full code [here](kotlinx-coroutines-javafx/test/guide/example-ui-actor-01.kt). 235 236Note that each time the circle is clicked, it starts a new coroutine and they all compete to 237update the text. Try it. It does not look very good. We'll fix it later. 238 239> On Android, the corresponding extension can be written for `View` class, so that the code 240 in `setup` function that is shown above can be used without changes. There is no `MouseEvent` 241 used in OnClickListener on Android, so it is omitted. 242 243```kotlin 244fun View.onClick(action: suspend () -> Unit) { 245 setOnClickListener { 246 GlobalScope.launch(Dispatchers.Main) { 247 action() 248 } 249 } 250} 251``` 252 253<!--- CLEAR --> 254 255### At most one concurrent job 256 257We can cancel an active job before starting a new one to ensure that at most one coroutine is animating 258the countdown. However, it is generally not the best idea. The [cancel][Job.cancel] function serves only as a signal 259to abort a coroutine. Cancellation is cooperative and a coroutine may, at the moment, be doing something non-cancellable 260or otherwise ignore a cancellation signal. A better solution is to use an [actor] for tasks that should 261not be performed concurrently. Let us change `onClick` extension implementation: 262 263```kotlin 264fun Node.onClick(action: suspend (MouseEvent) -> Unit) { 265 // launch one actor to handle all events on this node 266 val eventActor = GlobalScope.actor<MouseEvent>(Dispatchers.Main) { 267 for (event in channel) action(event) // pass event to action 268 } 269 // install a listener to offer events to this actor 270 onMouseClicked = EventHandler { event -> 271 eventActor.trySend(event) 272 } 273} 274``` 275 276> You can get the full code [here](kotlinx-coroutines-javafx/test/guide/example-ui-actor-02.kt). 277 278The key idea that underlies an integration of an actor coroutine and a regular event handler is that 279there is an [trySend][SendChannel.trySend] function on [SendChannel] that does not wait. It sends an element to the actor immediately, 280if it is possible, or discards an element otherwise. A `trySend` actually returns a `ChanneResult` instance which we ignore here. 281 282Try clicking repeatedly on a circle in this version of the code. The clicks are just ignored while the countdown 283animation is running. This happens because the actor is busy with an animation and does not receive from its channel. 284By default, an actor's mailbox is backed by `RendezvousChannel`, whose `trySend` operation succeeds only when 285the `receive` is active. 286 287> On Android, there is `View` sent in OnClickListener, so we send the `View` to the actor as a signal. 288 The corresponding extension for `View` class looks like this: 289 290```kotlin 291fun View.onClick(action: suspend (View) -> Unit) { 292 // launch one actor 293 val eventActor = GlobalScope.actor<View>(Dispatchers.Main) { 294 for (event in channel) action(event) 295 } 296 // install a listener to activate this actor 297 setOnClickListener { 298 eventActor.trySend(it) 299 } 300} 301``` 302 303<!--- CLEAR --> 304 305 306### Event conflation 307 308Sometimes it is more appropriate to process the most recent event, instead of just ignoring events while we were busy 309processing the previous one. The [actor] coroutine builder accepts an optional `capacity` parameter that 310controls the implementation of the channel that this actor is using for its mailbox. The description of all 311the available choices is given in documentation of the [`Channel()`][Channel] factory function. 312 313Let us change the code to use a conflated channel by passing [Channel.CONFLATED][Channel.Factory.CONFLATED] capacity value. The 314change is only to the line that creates an actor: 315 316```kotlin 317fun Node.onClick(action: suspend (MouseEvent) -> Unit) { 318 // launch one actor to handle all events on this node 319 val eventActor = GlobalScope.actor<MouseEvent>(Dispatchers.Main, capacity = Channel.CONFLATED) { // <--- Changed here 320 for (event in channel) action(event) // pass event to action 321 } 322 // install a listener to send events to this actor 323 onMouseClicked = EventHandler { event -> 324 eventActor.trySend(event) 325 } 326} 327``` 328 329> You can get full JavaFx code [here](kotlinx-coroutines-javafx/test/guide/example-ui-actor-03.kt). 330 On Android you need to update `val eventActor = ...` line from the previous example. 331 332Now, if a circle is clicked while the animation is running, it restarts animation after the end of it. Just once. 333Repeated clicks while the animation is running are _conflated_ and only the most recent event gets to be 334processed. 335 336This is also a desired behaviour for UI applications that have to react to incoming high-frequency 337event streams by updating their UI based on the most recently received update. A coroutine that is using 338a conflated channel (`capacity = Channel.CONFLATED`, or a buffered channel with 339`onBufferOverflow = DROP_OLDEST` or `onBufferOverflow = DROP_LATEST`) avoids delays 340that are usually introduced by buffering of events. 341 342You can experiment with `capacity` parameter in the above line to see how it affects the behaviour of the code. 343Setting `capacity = Channel.UNLIMITED` creates a coroutine with an unbounded mailbox that buffers all 344events. In this case, the animation runs as many times as the circle is clicked. 345 346## Blocking operations 347 348This section explains how to use UI coroutines with thread-blocking operations. 349 350### The problem of UI freezes 351 352It would have been great if all APIs out there were written as suspending functions that never blocks an 353execution thread. However, it is quite often not the case. Sometimes you need to do a CPU-consuming computation 354or just need to invoke some 3rd party APIs for network access, for example, that blocks the invoker thread. 355You cannot do that from the main UI thread nor from the UI-confined coroutine directly, because that would 356block the main UI thread and cause the freeze up of the UI. 357 358<!--- INCLUDE .*/example-ui-blocking-([0-9]+).kt 359fun Node.onClick(action: suspend (MouseEvent) -> Unit) { 360 val eventActor = GlobalScope.actor<MouseEvent>(Dispatchers.Main, capacity = Channel.CONFLATED) { 361 for (event in channel) action(event) // pass event to action 362 } 363 onMouseClicked = EventHandler { event -> 364 eventActor.trySend(event) 365 } 366} 367--> 368 369The following example illustrates the problem. We are going to use `onClick` extension with UI-confined 370event-conflating actor from the last section to process the last click in the main UI thread. 371For this example, we are going to 372perform naive computation of [Fibonacci numbers](https://en.wikipedia.org/wiki/Fibonacci_number): 373 374```kotlin 375fun fib(x: Int): Int = 376 if (x <= 1) x else fib(x - 1) + fib(x - 2) 377``` 378 379We'll be computing larger and larger Fibonacci number each time the circle is clicked. 380To make the UI freeze more obvious, there is also a fast counting animation that is always running 381and is constantly updating the text in the main UI dispatcher: 382 383```kotlin 384fun setup(hello: Text, fab: Circle) { 385 var result = "none" // the last result 386 // counting animation 387 GlobalScope.launch(Dispatchers.Main) { 388 var counter = 0 389 while (true) { 390 hello.text = "${++counter}: $result" 391 delay(100) // update the text every 100ms 392 } 393 } 394 // compute the next fibonacci number of each click 395 var x = 1 396 fab.onClick { 397 result = "fib($x) = ${fib(x)}" 398 x++ 399 } 400} 401``` 402 403> You can get full JavaFx code [here](kotlinx-coroutines-javafx/test/guide/example-ui-blocking-01.kt). 404 You can just copy the `fib` function and the body of the `setup` function to your Android project. 405 406Try clicking on the circle in this example. After around 30-40th click our naive computation is going to become 407quite slow and you would immediately see how the main UI thread freezes, because the animation stops running 408during UI freeze. 409 410### Structured concurrency, lifecycle and coroutine parent-child hierarchy 411 412A typical UI application has a number of elements with a lifecycle. Windows, UI controls, activities, views, fragments 413and other visual elements are created and destroyed. A long-running coroutine, performing some IO or a background 414computation, can retain references to the corresponding UI elements for longer than it is needed, preventing garbage 415collection of the whole trees of UI objects that were already destroyed and will not be displayed anymore. 416 417The natural solution to this problem is to associate a [CoroutineScope] object with each UI object that has a 418lifecycle and create all the coroutines in the context of this scope. 419For the sake of simplicity, [MainScope()] factory can be used. It automatically provides `Dispatchers.Main` and 420a parent job for all the children coroutines. 421 422For example, in Android application an `Activity` is initially _created_ and is _destroyed_ when it is no longer 423needed and when its memory must be released. A natural solution is to attach an 424instance of a `CoroutineScope` to an instance of an `Activity`: 425 426<!--- CLEAR --> 427 428```kotlin 429class MainActivity : AppCompatActivity() { 430 private val scope = MainScope() 431 432 override fun onDestroy() { 433 super.onDestroy() 434 scope.cancel() 435 } 436 437 fun asyncShowData() = scope.launch { // Is invoked in UI context with Activity's scope as a parent 438 // actual implementation 439 } 440 441 suspend fun showIOData() { 442 val data = withContext(Dispatchers.IO) { 443 // compute data in background thread 444 } 445 withContext(Dispatchers.Main) { 446 // Show data in UI 447 } 448 } 449} 450``` 451 452Every coroutine launched from within a `MainActivity` has its job as a parent and is immediately cancelled when 453activity is destroyed. 454 455> Note, that Android has first-party support for coroutine scope in all entities with the lifecycle. 456See [the corresponding documentation](https://developer.android.com/topic/libraries/architecture/coroutines#lifecyclescope). 457 458Parent-child relation between jobs forms a hierarchy. A coroutine that performs some background job on behalf of 459the activity can create further children coroutines. The whole tree of coroutines gets cancelled 460when the parent job is cancelled. An example of that is shown in the 461["Children of a coroutine"](../docs/topics/coroutine-context-and-dispatchers.md#children-of-a-coroutine) section of the guide to coroutines. 462 463<!--- CLEAR --> 464 465### Blocking operations 466 467The fix for the blocking operations on the main UI thread is quite straightforward with coroutines. We'll 468convert our "blocking" `fib` function to a non-blocking suspending function that runs the computation in 469the background thread by using [withContext] function to change its execution context to [Dispatchers.Default] that is 470backed by the background pool of threads. 471Notice, that `fib` function is now marked with `suspend` modifier. It does not block the coroutine that 472it is invoked from anymore, but suspends its execution when the computation in the background thread is working: 473 474<!--- INCLUDE .*/example-ui-blocking-0[23].kt 475 476fun setup(hello: Text, fab: Circle) { 477 var result = "none" // the last result 478 // counting animation 479 GlobalScope.launch(Dispatchers.Main) { 480 var counter = 0 481 while (true) { 482 hello.text = "${++counter}: $result" 483 delay(100) // update the text every 100ms 484 } 485 } 486 // compute next fibonacci number of each click 487 var x = 1 488 fab.onClick { 489 result = "fib($x) = ${fib(x)}" 490 x++ 491 } 492} 493--> 494 495```kotlin 496suspend fun fib(x: Int): Int = withContext(Dispatchers.Default) { 497 if (x <= 1) x else fib(x - 1) + fib(x - 2) 498} 499``` 500 501> You can get the full code [here](kotlinx-coroutines-javafx/test/guide/example-ui-blocking-02.kt). 502 503You can run this code and verify that UI is not frozen while large Fibonacci numbers are being computed. 504However, this code computes `fib` somewhat slower, because every recursive call to `fib` goes via `withContext`. This is 505not a big problem in practice, because `withContext` is smart enough to check that the coroutine is already running 506in the required context and avoids overhead of dispatching coroutine to a different thread again. It is an 507overhead nonetheless, which is visible on this primitive code that does nothing else, but only adds integers 508in between invocations to `withContext`. For some more substantial code, the overhead of an extra `withContext` invocation is 509not going to be significant. 510 511Still, this particular `fib` implementation can be made to run as fast as before, but in the background thread, by renaming 512the original `fib` function to `fibBlocking` and defining `fib` with `withContext` wrapper on top of `fibBlocking`: 513 514```kotlin 515suspend fun fib(x: Int): Int = withContext(Dispatchers.Default) { 516 fibBlocking(x) 517} 518 519fun fibBlocking(x: Int): Int = 520 if (x <= 1) x else fibBlocking(x - 1) + fibBlocking(x - 2) 521``` 522 523> You can get the full code [here](kotlinx-coroutines-javafx/test/guide/example-ui-blocking-03.kt). 524 525You can now enjoy full-speed naive Fibonacci computation without blocking the main UI thread. 526All we need is `withContext(Dispatchers.Default)`. 527 528Note that because the `fib` function is invoked from the single actor in our code, there is at most one concurrent 529computation of it at any given time, so this code has a natural limit on the resource utilization. 530It can saturate at most one CPU core. 531 532## Advanced topics 533 534This section covers various advanced topics. 535 536### Starting coroutine in UI event handlers without dispatch 537 538Let us write the following code in `setup` to visualize the order of execution when coroutine is launched 539from the UI thread: 540 541<!--- CLEAR --> 542 543```kotlin 544fun setup(hello: Text, fab: Circle) { 545 fab.onMouseClicked = EventHandler { 546 println("Before launch") 547 GlobalScope.launch(Dispatchers.Main) { 548 println("Inside coroutine") 549 delay(100) 550 println("After delay") 551 } 552 println("After launch") 553 } 554} 555``` 556 557> You can get full JavaFx code [here](kotlinx-coroutines-javafx/test/guide/example-ui-advanced-01.kt). 558 559When we start this code and click on a pinkish circle, the following messages are printed to the console: 560 561```text 562Before launch 563After launch 564Inside coroutine 565After delay 566``` 567 568As you can see, execution immediately continues after [launch], while the coroutine gets posted onto the main UI thread 569for execution later. All UI dispatchers in `kotlinx.coroutines` are implemented this way. Why so? 570 571Basically, the choice here is between "JS-style" asynchronous approach (async actions 572are always postponed to be executed later in the event dispatch thread) and "C#-style" approach 573(async actions are executed in the invoker thread until the first suspension point). 574While, C# approach seems to be more efficient, it ends up with recommendations like 575"use `yield` if you need to ....". This is error-prone. JS-style approach is more consistent 576and does not require programmers to think about whether they need to yield or not. 577 578However, in this particular case when coroutine is started from an event handler and there is no other code around it, 579this extra dispatch does indeed add an extra overhead without bringing any additional value. 580In this case an optional [CoroutineStart] parameter to [launch], [async] and [actor] coroutine builders 581can be used for performance optimization. 582Setting it to the value of [CoroutineStart.UNDISPATCHED] has the effect of starting to execute 583coroutine immediately until its first suspension point as the following example shows: 584 585```kotlin 586fun setup(hello: Text, fab: Circle) { 587 fab.onMouseClicked = EventHandler { 588 println("Before launch") 589 GlobalScope.launch(Dispatchers.Main, CoroutineStart.UNDISPATCHED) { // <--- Notice this change 590 println("Inside coroutine") 591 delay(100) // <--- And this is where coroutine suspends 592 println("After delay") 593 } 594 println("After launch") 595 } 596} 597``` 598 599> You can get full JavaFx code [here](kotlinx-coroutines-javafx/test/guide/example-ui-advanced-02.kt). 600 601It prints the following messages on click, confirming that code in the coroutine starts to execute immediately: 602 603```text 604Before launch 605Inside coroutine 606After launch 607After delay 608``` 609 610<!--- MODULE kotlinx-coroutines-core --> 611<!--- INDEX kotlinx.coroutines --> 612 613[launch]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/launch.html 614[delay]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/delay.html 615[Job]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-job/index.html 616[Job.cancel]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/cancel.html 617[CoroutineScope]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-coroutine-scope/index.html 618[MainScope()]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-main-scope.html 619[withContext]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/with-context.html 620[Dispatchers.Default]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-dispatchers/-default.html 621[CoroutineStart]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-coroutine-start/index.html 622[async]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/async.html 623[CoroutineStart.UNDISPATCHED]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-coroutine-start/-u-n-d-i-s-p-a-t-c-h-e-d/index.html 624 625<!--- INDEX kotlinx.coroutines.channels --> 626 627[actor]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.channels/actor.html 628[SendChannel.trySend]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.channels/-send-channel/try-send.html 629[SendChannel]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.channels/-send-channel/index.html 630[Channel]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.channels/-channel/index.html 631[Channel.Factory.CONFLATED]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.channels/-channel/-factory/-c-o-n-f-l-a-t-e-d.html 632 633<!--- MODULE kotlinx-coroutines-javafx --> 634<!--- INDEX kotlinx.coroutines.javafx --> 635 636[kotlinx.coroutines.Dispatchers.JavaFx]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-javafx/kotlinx.coroutines.javafx/-java-fx.html 637 638<!--- MODULE kotlinx-coroutines-android --> 639<!--- INDEX kotlinx.coroutines.android --> 640<!--- END --> 641