Skip to content

Commit f724f6e

Browse files
committed
Starting coroutine in UI event handlers without dispatch in UI guide
1 parent ecda27f commit f724f6e

File tree

4 files changed

+235
-7
lines changed

4 files changed

+235
-7
lines changed

knit/src/Knit.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -168,13 +168,13 @@ fun knit(markdownFileName: String) {
168168
}
169169
}
170170
if (inLine.startsWith(CODE_START)) {
171-
require(testLines.isEmpty()) { "Previous test was not emitted with $TEST_DIRECTIVE" }
171+
require(testOut == null || testLines.isEmpty()) { "Previous test was not emitted with $TEST_DIRECTIVE" }
172172
codeLines += ""
173173
readUntilTo(CODE_END, codeLines)
174174
continue@mainLoop
175175
}
176176
if (inLine.startsWith(TEST_START)) {
177-
require(testLines.isEmpty()) { "Previous test was not emitted with $TEST_DIRECTIVE" }
177+
require(testOut == null || testLines.isEmpty()) { "Previous test was not emitted with $TEST_DIRECTIVE" }
178178
readUntilTo(TEST_END, testLines)
179179
continue@mainLoop
180180
}

ui/coroutines-guide-ui.md

Lines changed: 83 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -104,8 +104,9 @@ context object for your favourite UI library, even if it is not included out of
104104
* [Blocking operations](#blocking-operations)
105105
* [The problem of UI freezes](#the-problem-of-ui-freezes)
106106
* [Blocking operations](#blocking-operations)
107-
* [Lifecycle](#lifecycle)
108-
* [Using coroutine parent-child hierarchy](#using-coroutine-parent-child-hierarchy)
107+
* [Advanced topics](#advanced-topics)
108+
* [Lifecycle and coroutine parent-child hierarchy](#lifecycle-and-coroutine-parent-child-hierarchy)
109+
* [Starting coroutine in UI event handlers without dispatch](#starting-coroutine-in-ui-event-handlers-without-dispatch)
109110

110111
<!--- END_TOC -->
111112

@@ -538,11 +539,11 @@ Note, that because the `fib` function is invoked from the single actor in our co
538539
computation of it at any given time, so this code has a natural limit on the resource utilization.
539540
It can saturate at most one CPU core.
540541

541-
## Lifecycle
542+
## Advanced topics
542543

543-
This section outlines an approach to life-cycle management with coroutines.
544+
This section covers various advanced topics.
544545

545-
### Using coroutine parent-child hierarchy
546+
### Lifecycle and coroutine parent-child hierarchy
546547

547548
A typical UI application has a number of elements with a lifecycle. Windows, UI controls, activities, views, fragments
548549
and other visual elements are created and destroyed. A long-running coroutine, performing some IO or a background
@@ -618,6 +619,80 @@ Parent-child relation between jobs forms a hierarchy. A coroutine that performs
618619
the view and in its context can create further children coroutines. The whole tree of coroutines gets cancelled
619620
when the parent job is cancelled. An example of that is shown in
620621
["Children of a coroutine"](../coroutines-guide.md#children-of-a-coroutine) section of the guide to coroutines.
622+
623+
### Starting coroutine in UI event handlers without dispatch
624+
625+
Let us write the following code in `setup` to visualize the order of execution when coroutine is launched
626+
from the UI thread:
627+
628+
<!--- CLEAR -->
629+
630+
```kotlin
631+
fun setup(hello: Text, fab: Circle) {
632+
fab.onMouseClicked = EventHandler {
633+
println("Before launch")
634+
launch(UI) {
635+
println("Inside coroutine")
636+
delay(100)
637+
println("After delay")
638+
}
639+
println("After launch")
640+
}
641+
}
642+
```
643+
644+
> You can get full JavaFx code [here](kotlinx-coroutines-javafx/src/test/kotlin/guide/example-ui-advanced-01.kt).
645+
646+
When we start this code and click on a pinkish circle, the following messages are printed to the console:
647+
648+
```text
649+
Before launch
650+
After launch
651+
Inside coroutine
652+
After delay
653+
```
654+
655+
As you can see, execution immediately continues after [launch], while the coroutine gets posted onto UI thread
656+
for execution later. All UI dispatchers in `kotlinx.coroutines` are implemented this way. Why so?
657+
658+
Basically, the choice here is between "JS-style" asynchronous approach (async actions
659+
are always postponed to be executed later in the even dispatch thread) and "C#-style" approach
660+
(async actions are executed in the invoker thread until the first suspension point).
661+
While, C# approach seems to be more efficient, it ends up with recommendations like
662+
"use `yield` if you need to ....". This is error-prone. JS-style approach is more consistent
663+
and does not require programmers to think about whether they need to yield or not.
664+
665+
However, in this particular case when coroutine is started from an event handler and there is no other code around it,
666+
this extra dispatch does indeed add an extra overhead without bringing any additional value.
667+
In this case an optional [CoroutineStart] parameter to [launch], [async] and [actor] coroutine builders
668+
can be used for performance optimization.
669+
Setting it to the value of [CoroutineStart.UNDISPATCHED] has the effect of starting to execute
670+
coroutine immediately until its first suspension point as the following example shows:
671+
672+
```kotlin
673+
fun setup(hello: Text, fab: Circle) {
674+
fab.onMouseClicked = EventHandler {
675+
println("Before launch")
676+
launch(UI, CoroutineStart.UNDISPATCHED) { // <--- Notice this change
677+
println("Inside coroutine")
678+
delay(100) // <--- And this is where coroutine suspends
679+
println("After delay")
680+
}
681+
println("After launch")
682+
}
683+
}
684+
```
685+
686+
> You can get full JavaFx code [here](kotlinx-coroutines-javafx/src/test/kotlin/guide/example-ui-advanced-02.kt).
687+
688+
It prints the following messages on click, confirming that code in the coroutine starts to execute immediately:
689+
690+
```text
691+
Before launch
692+
Inside coroutine
693+
After launch
694+
After delay
695+
```
621696

622697
<!--- SITE_ROOT https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core -->
623698
<!--- DOCS_ROOT kotlinx-coroutines-core/target/dokka/kotlinx-coroutines-core -->
@@ -629,6 +704,9 @@ when the parent job is cancelled. An example of that is shown in
629704
[run]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.experimental/run.html
630705
[CommonPool]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.experimental/-common-pool/index.html
631706
[NonCancellable]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.experimental/-non-cancellable/index.html
707+
[CoroutineStart]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.experimental/-coroutine-start/index.html
708+
[async]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.experimental/async.html
709+
[CoroutineStart.UNDISPATCHED]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.experimental/-coroutine-start/-u-n-d-i-s-p-a-t-c-h-e-d.html
632710
<!--- INDEX kotlinx.coroutines.experimental.channels -->
633711
[actor]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.experimental.channels/actor.html
634712
[SendChannel.offer]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.experimental.channels/-send-channel/offer.html
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
/*
2+
* Copyright 2016-2017 JetBrains s.r.o.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
// This file was automatically generated from coroutines-guide-ui.md by Knit tool. Do not edit.
18+
package guide.ui.advanced.example01
19+
20+
import kotlinx.coroutines.experimental.*
21+
import kotlinx.coroutines.experimental.channels.*
22+
import kotlinx.coroutines.experimental.javafx.JavaFx as UI
23+
import javafx.application.Application
24+
import javafx.event.EventHandler
25+
import javafx.geometry.*
26+
import javafx.scene.*
27+
import javafx.scene.input.MouseEvent
28+
import javafx.scene.layout.StackPane
29+
import javafx.scene.paint.Color
30+
import javafx.scene.shape.Circle
31+
import javafx.scene.text.Text
32+
import javafx.stage.Stage
33+
34+
fun main(args: Array<String>) {
35+
Application.launch(ExampleApp::class.java, *args)
36+
}
37+
38+
class ExampleApp : Application() {
39+
val hello = Text("Hello World!").apply {
40+
fill = Color.valueOf("#C0C0C0")
41+
}
42+
43+
val fab = Circle(20.0, Color.valueOf("#FF4081"))
44+
45+
val root = StackPane().apply {
46+
children += hello
47+
children += fab
48+
StackPane.setAlignment(hello, Pos.CENTER)
49+
StackPane.setAlignment(fab, Pos.BOTTOM_RIGHT)
50+
StackPane.setMargin(fab, Insets(15.0))
51+
}
52+
53+
val scene = Scene(root, 240.0, 380.0).apply {
54+
fill = Color.valueOf("#303030")
55+
}
56+
57+
override fun start(stage: Stage) {
58+
stage.title = "Example"
59+
stage.scene = scene
60+
stage.show()
61+
setup(hello, fab)
62+
}
63+
}
64+
65+
fun setup(hello: Text, fab: Circle) {
66+
fab.onMouseClicked = EventHandler {
67+
println("Before launch")
68+
launch(UI) {
69+
println("Inside coroutine")
70+
delay(100)
71+
println("After delay")
72+
}
73+
println("After launch")
74+
}
75+
}
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
/*
2+
* Copyright 2016-2017 JetBrains s.r.o.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
// This file was automatically generated from coroutines-guide-ui.md by Knit tool. Do not edit.
18+
package guide.ui.advanced.example02
19+
20+
import kotlinx.coroutines.experimental.*
21+
import kotlinx.coroutines.experimental.channels.*
22+
import kotlinx.coroutines.experimental.javafx.JavaFx as UI
23+
import javafx.application.Application
24+
import javafx.event.EventHandler
25+
import javafx.geometry.*
26+
import javafx.scene.*
27+
import javafx.scene.input.MouseEvent
28+
import javafx.scene.layout.StackPane
29+
import javafx.scene.paint.Color
30+
import javafx.scene.shape.Circle
31+
import javafx.scene.text.Text
32+
import javafx.stage.Stage
33+
34+
fun main(args: Array<String>) {
35+
Application.launch(ExampleApp::class.java, *args)
36+
}
37+
38+
class ExampleApp : Application() {
39+
val hello = Text("Hello World!").apply {
40+
fill = Color.valueOf("#C0C0C0")
41+
}
42+
43+
val fab = Circle(20.0, Color.valueOf("#FF4081"))
44+
45+
val root = StackPane().apply {
46+
children += hello
47+
children += fab
48+
StackPane.setAlignment(hello, Pos.CENTER)
49+
StackPane.setAlignment(fab, Pos.BOTTOM_RIGHT)
50+
StackPane.setMargin(fab, Insets(15.0))
51+
}
52+
53+
val scene = Scene(root, 240.0, 380.0).apply {
54+
fill = Color.valueOf("#303030")
55+
}
56+
57+
override fun start(stage: Stage) {
58+
stage.title = "Example"
59+
stage.scene = scene
60+
stage.show()
61+
setup(hello, fab)
62+
}
63+
}
64+
65+
fun setup(hello: Text, fab: Circle) {
66+
fab.onMouseClicked = EventHandler {
67+
println("Before launch")
68+
launch(UI, CoroutineStart.UNDISPATCHED) { // <--- Notice this change
69+
println("Inside coroutine")
70+
delay(100) // <--- And this is where coroutine suspends
71+
println("After delay")
72+
}
73+
println("After launch")
74+
}
75+
}

0 commit comments

Comments
 (0)