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/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![UI example for JavaFx](ui-example-javafx.png) 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![UI example for Android](ui-example-android.png) 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.4.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.offer(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 [offer][SendChannel.offer] function on [SendChannel] that does not wait. It sends an element to the actor immediately, 280if it is possible, or discards an element otherwise. An `offer` actually returns a `Boolean` result 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 `offer` 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.offer(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] 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 offer events to this actor 323 onMouseClicked = EventHandler { event -> 324 eventActor.offer(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 338`ConflatedChannel` avoids delays that are usually introduced by buffering of events. 339 340You can experiment with `capacity` parameter in the above line to see how it affects the behaviour of the code. 341Setting `capacity = Channel.UNLIMITED` creates a coroutine with `LinkedListChannel` mailbox that buffers all 342events. In this case, the animation runs as many times as the circle is clicked. 343 344## Blocking operations 345 346This section explains how to use UI coroutines with thread-blocking operations. 347 348### The problem of UI freezes 349 350It would have been great if all APIs out there were written as suspending functions that never blocks an 351execution thread. However, it is quite often not the case. Sometimes you need to do a CPU-consuming computation 352or just need to invoke some 3rd party APIs for network access, for example, that blocks the invoker thread. 353You cannot do that from the main UI thread nor from the UI-confined coroutine directly, because that would 354block the main UI thread and cause the freeze up of the UI. 355 356<!--- INCLUDE .*/example-ui-blocking-([0-9]+).kt 357fun Node.onClick(action: suspend (MouseEvent) -> Unit) { 358 val eventActor = GlobalScope.actor<MouseEvent>(Dispatchers.Main, capacity = Channel.CONFLATED) { 359 for (event in channel) action(event) // pass event to action 360 } 361 onMouseClicked = EventHandler { event -> 362 eventActor.offer(event) 363 } 364} 365--> 366 367The following example illustrates the problem. We are going to use `onClick` extension with UI-confined 368event-conflating actor from the last section to process the last click in the main UI thread. 369For this example, we are going to 370perform naive computation of [Fibonacci numbers](https://en.wikipedia.org/wiki/Fibonacci_number): 371 372```kotlin 373fun fib(x: Int): Int = 374 if (x <= 1) x else fib(x - 1) + fib(x - 2) 375``` 376 377We'll be computing larger and larger Fibonacci number each time the circle is clicked. 378To make the UI freeze more obvious, there is also a fast counting animation that is always running 379and is constantly updating the text in the main UI dispatcher: 380 381```kotlin 382fun setup(hello: Text, fab: Circle) { 383 var result = "none" // the last result 384 // counting animation 385 GlobalScope.launch(Dispatchers.Main) { 386 var counter = 0 387 while (true) { 388 hello.text = "${++counter}: $result" 389 delay(100) // update the text every 100ms 390 } 391 } 392 // compute the next fibonacci number of each click 393 var x = 1 394 fab.onClick { 395 result = "fib($x) = ${fib(x)}" 396 x++ 397 } 398} 399``` 400 401> You can get full JavaFx code [here](kotlinx-coroutines-javafx/test/guide/example-ui-blocking-01.kt). 402 You can just copy the `fib` function and the body of the `setup` function to your Android project. 403 404Try clicking on the circle in this example. After around 30-40th click our naive computation is going to become 405quite slow and you would immediately see how the main UI thread freezes, because the animation stops running 406during UI freeze. 407 408### Structured concurrency, lifecycle and coroutine parent-child hierarchy 409 410A typical UI application has a number of elements with a lifecycle. Windows, UI controls, activities, views, fragments 411and other visual elements are created and destroyed. A long-running coroutine, performing some IO or a background 412computation, can retain references to the corresponding UI elements for longer than it is needed, preventing garbage 413collection of the whole trees of UI objects that were already destroyed and will not be displayed anymore. 414 415The natural solution to this problem is to associate a [CoroutineScope] object with each UI object that has a 416lifecycle and create all the coroutines in the context of this scope. 417For the sake of simplicity, [MainScope()] factory can be used. It automatically provides `Dispatchers.Main` and 418a parent job for all the children coroutines. 419 420For example, in Android application an `Activity` is initially _created_ and is _destroyed_ when it is no longer 421needed and when its memory must be released. A natural solution is to attach an 422instance of a `CoroutineScope` to an instance of an `Activity`: 423 424<!--- CLEAR --> 425 426```kotlin 427class MainActivity : AppCompatActivity() { 428 private val scope = MainScope() 429 430 override fun onDestroy() { 431 super.onDestroy() 432 scope.cancel() 433 } 434 435 fun asyncShowData() = scope.launch { // Is invoked in UI context with Activity's scope as a parent 436 // actual implementation 437 } 438 439 suspend fun showIOData() { 440 val data = withContext(Dispatchers.IO) { 441 // compute data in background thread 442 } 443 withContext(Dispatchers.Main) { 444 // Show data in UI 445 } 446 } 447} 448``` 449 450Every coroutine launched from within a `MainActivity` has its job as a parent and is immediately cancelled when 451activity is destroyed. 452 453> Note, that Android has first-party support for coroutine scope in all entities with the lifecycle. 454See [the corresponding documentation](https://developer.android.com/topic/libraries/architecture/coroutines#lifecyclescope). 455 456Parent-child relation between jobs forms a hierarchy. A coroutine that performs some background job on behalf of 457the activity can create further children coroutines. The whole tree of coroutines gets cancelled 458when the parent job is cancelled. An example of that is shown in the 459["Children of a coroutine"](../docs/coroutine-context-and-dispatchers.md#children-of-a-coroutine) section of the guide to coroutines. 460 461<!--- CLEAR --> 462 463### Blocking operations 464 465The fix for the blocking operations on the main UI thread is quite straightforward with coroutines. We'll 466convert our "blocking" `fib` function to a non-blocking suspending function that runs the computation in 467the background thread by using [withContext] function to change its execution context to [Dispatchers.Default] that is 468backed by the background pool of threads. 469Notice, that `fib` function is now marked with `suspend` modifier. It does not block the coroutine that 470it is invoked from anymore, but suspends its execution when the computation in the background thread is working: 471 472<!--- INCLUDE .*/example-ui-blocking-0[23].kt 473 474fun setup(hello: Text, fab: Circle) { 475 var result = "none" // the last result 476 // counting animation 477 GlobalScope.launch(Dispatchers.Main) { 478 var counter = 0 479 while (true) { 480 hello.text = "${++counter}: $result" 481 delay(100) // update the text every 100ms 482 } 483 } 484 // compute next fibonacci number of each click 485 var x = 1 486 fab.onClick { 487 result = "fib($x) = ${fib(x)}" 488 x++ 489 } 490} 491--> 492 493```kotlin 494suspend fun fib(x: Int): Int = withContext(Dispatchers.Default) { 495 if (x <= 1) x else fib(x - 1) + fib(x - 2) 496} 497``` 498 499> You can get the full code [here](kotlinx-coroutines-javafx/test/guide/example-ui-blocking-02.kt). 500 501You can run this code and verify that UI is not frozen while large Fibonacci numbers are being computed. 502However, this code computes `fib` somewhat slower, because every recursive call to `fib` goes via `withContext`. This is 503not a big problem in practice, because `withContext` is smart enough to check that the coroutine is already running 504in the required context and avoids overhead of dispatching coroutine to a different thread again. It is an 505overhead nonetheless, which is visible on this primitive code that does nothing else, but only adds integers 506in between invocations to `withContext`. For some more substantial code, the overhead of an extra `withContext` invocation is 507not going to be significant. 508 509Still, this particular `fib` implementation can be made to run as fast as before, but in the background thread, by renaming 510the original `fib` function to `fibBlocking` and defining `fib` with `withContext` wrapper on top of `fibBlocking`: 511 512```kotlin 513suspend fun fib(x: Int): Int = withContext(Dispatchers.Default) { 514 fibBlocking(x) 515} 516 517fun fibBlocking(x: Int): Int = 518 if (x <= 1) x else fibBlocking(x - 1) + fibBlocking(x - 2) 519``` 520 521> You can get the full code [here](kotlinx-coroutines-javafx/test/guide/example-ui-blocking-03.kt). 522 523You can now enjoy full-speed naive Fibonacci computation without blocking the main UI thread. 524All we need is `withContext(Dispatchers.Default)`. 525 526Note that because the `fib` function is invoked from the single actor in our code, there is at most one concurrent 527computation of it at any given time, so this code has a natural limit on the resource utilization. 528It can saturate at most one CPU core. 529 530## Advanced topics 531 532This section covers various advanced topics. 533 534### Starting coroutine in UI event handlers without dispatch 535 536Let us write the following code in `setup` to visualize the order of execution when coroutine is launched 537from the UI thread: 538 539<!--- CLEAR --> 540 541```kotlin 542fun setup(hello: Text, fab: Circle) { 543 fab.onMouseClicked = EventHandler { 544 println("Before launch") 545 GlobalScope.launch(Dispatchers.Main) { 546 println("Inside coroutine") 547 delay(100) 548 println("After delay") 549 } 550 println("After launch") 551 } 552} 553``` 554 555> You can get full JavaFx code [here](kotlinx-coroutines-javafx/test/guide/example-ui-advanced-01.kt). 556 557When we start this code and click on a pinkish circle, the following messages are printed to the console: 558 559```text 560Before launch 561After launch 562Inside coroutine 563After delay 564``` 565 566As you can see, execution immediately continues after [launch], while the coroutine gets posted onto the main UI thread 567for execution later. All UI dispatchers in `kotlinx.coroutines` are implemented this way. Why so? 568 569Basically, the choice here is between "JS-style" asynchronous approach (async actions 570are always postponed to be executed later in the event dispatch thread) and "C#-style" approach 571(async actions are executed in the invoker thread until the first suspension point). 572While, C# approach seems to be more efficient, it ends up with recommendations like 573"use `yield` if you need to ....". This is error-prone. JS-style approach is more consistent 574and does not require programmers to think about whether they need to yield or not. 575 576However, in this particular case when coroutine is started from an event handler and there is no other code around it, 577this extra dispatch does indeed add an extra overhead without bringing any additional value. 578In this case an optional [CoroutineStart] parameter to [launch], [async] and [actor] coroutine builders 579can be used for performance optimization. 580Setting it to the value of [CoroutineStart.UNDISPATCHED] has the effect of starting to execute 581coroutine immediately until its first suspension point as the following example shows: 582 583```kotlin 584fun setup(hello: Text, fab: Circle) { 585 fab.onMouseClicked = EventHandler { 586 println("Before launch") 587 GlobalScope.launch(Dispatchers.Main, CoroutineStart.UNDISPATCHED) { // <--- Notice this change 588 println("Inside coroutine") 589 delay(100) // <--- And this is where coroutine suspends 590 println("After delay") 591 } 592 println("After launch") 593 } 594} 595``` 596 597> You can get full JavaFx code [here](kotlinx-coroutines-javafx/test/guide/example-ui-advanced-02.kt). 598 599It prints the following messages on click, confirming that code in the coroutine starts to execute immediately: 600 601```text 602Before launch 603Inside coroutine 604After launch 605After delay 606``` 607 608<!--- MODULE kotlinx-coroutines-core --> 609<!--- INDEX kotlinx.coroutines --> 610[launch]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/launch.html 611[delay]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/delay.html 612[Job]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-job/index.html 613[Job.cancel]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-job/cancel.html 614[CoroutineScope]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-coroutine-scope/index.html 615[MainScope()]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-main-scope.html 616[withContext]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/with-context.html 617[Dispatchers.Default]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-dispatchers/-default.html 618[CoroutineStart]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-coroutine-start/index.html 619[async]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/async.html 620[CoroutineStart.UNDISPATCHED]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-coroutine-start/-u-n-d-i-s-p-a-t-c-h-e-d.html 621<!--- INDEX kotlinx.coroutines.channels --> 622[actor]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.channels/actor.html 623[SendChannel.offer]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.channels/-send-channel/offer.html 624[SendChannel]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.channels/-send-channel/index.html 625[Channel]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.channels/-channel/index.html 626[Channel.CONFLATED]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.channels/-channel/-c-o-n-f-l-a-t-e-d.html 627<!--- MODULE kotlinx-coroutines-javafx --> 628<!--- INDEX kotlinx.coroutines.javafx --> 629[kotlinx.coroutines.Dispatchers.JavaFx]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-javafx/kotlinx.coroutines.javafx/kotlinx.coroutines.-dispatchers/-java-fx.html 630<!--- MODULE kotlinx-coroutines-android --> 631<!--- INDEX kotlinx.coroutines.android --> 632<!--- END --> 633