Skip to content

Commit 7deefb8

Browse files
committed
Guide on coroutines expanded (cancellation is covered) and linked from README page
1 parent e727cee commit 7deefb8

File tree

12 files changed

+350
-160
lines changed

12 files changed

+350
-160
lines changed

README.md

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,18 @@
11
# kotlinx.coroutines
22

33
Library support for Kotlin coroutines. This is a companion version for Kotlin 1.1.0-beta-18 release.
4-
It contains worked-out implementation of coroutine builders, suspending functions, and contexts that are
5-
used as examples in
6-
[Kotlin coroutines design document](https://github.com/Kotlin/kotlin-coroutines/blob/master/kotlin-coroutines-informal.md)
74

8-
See [change log](CHANGES.md) for a summary of changes between releases.
9-
10-
It consists of the following modules:
5+
## Modules and features
116

127
* `kotlinx-coroutines-core` module with core primitives to work with coroutines. It is designed to work on any JDK6+ and Android
138
and contains the following main pieces:
14-
* `launch(context) { ... }` to start a coroutine in the given context.
9+
* `launch(context) { ... }` to start a coroutine in the given context and get reference to its `Job`.
1510
* `run(context) { ... }` to switch to a different context inside a coroutine.
1611
* `runBlocking { ... }` to use asynchronous Kotlin APIs from a thread-blocking code.
17-
* `defer(context) { ... }` to get a deferred result of coroutine execution in a non-blocking way.
12+
* `defer(context) { ... }` to get a deferred result of coroutine execution in a non-blocking way
13+
via a light-weight future interface called `Deferred`.
1814
* `delay(...)` for a non-blocking sleep in coroutines.
19-
* `Here` and `CommonPool` contexts, `context` or a parent coroutine.
15+
* `CommonPool` and `Here` contexts, `context` or a parent coroutine.
2016
* `newSingleThreadContext(...)` and `newFixedThreadPoolContext(...)` functions,
2117
`Executor.toCoroutineDispatcher()` extension.
2218
* Cancellation support with `Job` interface and `suspendCancellableCoroutine` helper function.
@@ -38,6 +34,12 @@ and contains the following main pieces:
3834
[RxJava](https://github.com/ReactiveX/RxJava) with imperative coroutines and consume their values
3935
from inside coroutines. It is in very basic form now (example-only, not even close to production use)
4036

37+
## References and documentation
38+
39+
* [Coroutines guide](coroutines-guide.md)
40+
* [Change log](CHANGES.md)
41+
* [Coroutines design document](https://github.com/Kotlin/kotlin-coroutines/blob/master/kotlin-coroutines-informal.md)
42+
4143
## Using in your projects
4244

4345
> Note that these libraries are experimental and are subject to change.

coroutines-guide.md

Lines changed: 242 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,242 @@
1+
# Guide to kotlinx.coroutines by example
2+
3+
This is a short guide on core features of `kotlinx.coroutines` with a series of examples.
4+
5+
## Your first coroutine
6+
7+
Run the following code:
8+
9+
```kotlin
10+
fun main(args: Array<String>) {
11+
launch(CommonPool) { // create new coroutine in common thread pool
12+
delay(1000L) // non-blocking delay for 1 second (default time unit is ms)
13+
println("World!") // print after delay
14+
}
15+
println("Hello,") // main function continues while coroutine is delayed
16+
Thread.sleep(2000L) // block main thread for 2 seconds to keep JVM alive
17+
}
18+
```
19+
20+
> You can get full code [here](kotlinx-coroutines-core/src/test/kotlin/examples/example-01.kt)
21+
22+
Run this code:
23+
24+
```
25+
Hello,
26+
World!
27+
```
28+
29+
Essentially, coroutines are light-weight threads. You can achieve the same result replacing
30+
`launch(CommonPool) { ... }` with `thread { ... }` and `delay(...)` with `Thread.sleep(...)`. Try it.
31+
32+
If you start by replacing `launch(CommonPool)` by `thread`, the compiler produces the following error:
33+
34+
```
35+
Error: Kotlin: Suspend functions are only allowed to be called from a coroutine or another suspend function
36+
```
37+
38+
That is because `delay` is a special _suspending function_ that does not block a thread, but _suspends_
39+
coroutine and it can be only used from a coroutine.
40+
41+
## Bridging blocking and non-blocking worlds
42+
43+
The first example mixes _non-blocking_ `delay(...)` and _blocking_ `Thread.sleep(...)` in the same
44+
code of `main` function. It is easy to get lost. Let's cleanly separate blocking and non-blocking
45+
worlds by using `runBlocking { ... }`:
46+
47+
```kotlin
48+
fun main(args: Array<String>) = runBlocking<Unit> { // start main coroutine
49+
launch(CommonPool) { // create new coroutine in common thread pool
50+
delay(1000L)
51+
println("World!")
52+
}
53+
println("Hello,") // main coroutine continues while child is delayed
54+
delay(2000L) // non-blocking delay for 2 seconds to keep JVM alive
55+
}
56+
```
57+
58+
> You can get full code [here](kotlinx-coroutines-core/src/test/kotlin/examples/example-02.kt)
59+
60+
The result is the same, but this code uses only non-blocking `delay`.
61+
62+
`runBlocking { ... }` works as an adaptor that is used here to start the top-level main coroutine.
63+
The regular code outside of `runBlocking` _blocks_, until the coroutine inside `runBlocking` is active.
64+
65+
This is also a way to write unit-tests for suspending functions:
66+
67+
```kotlin
68+
class MyTest {
69+
@Test
70+
fun testMySuspendingFunction() = runBlocking<Unit> {
71+
// here we can use suspending functions using any assertion style that we like
72+
}
73+
}
74+
```
75+
76+
## Waiting for a job
77+
78+
Delaying for a time while a _child_ coroutine is working is not a good approach. Let's explicitly
79+
wait (in a non-blocking way) until the other coroutine that we have launched is complete:
80+
81+
```kotlin
82+
fun main(args: Array<String>) = runBlocking<Unit> {
83+
val job = launch(CommonPool) { // create new coroutine and keep a reference to its Job
84+
delay(1000L)
85+
println("World!")
86+
}
87+
println("Hello,")
88+
job.join() // wait until child coroutine completes
89+
}
90+
```
91+
92+
> You can get full code [here](kotlinx-coroutines-core/src/test/kotlin/examples/example-03.kt)
93+
94+
Now the result is still the same, but the code of the main coroutine is not tied to the duration of
95+
the child coroutine in any way. Much better.
96+
97+
## Extract function refactoring
98+
99+
Let's extract the block of code inside `launch(CommonPool} { ... }` into a separate function. When you
100+
perform "Extract function" refactoring on this code you get a new function with `suspend` modifier.
101+
That is your first _suspending function_. Suspending functions can be used inside coroutines
102+
just like regular functions, but their additional feature is that they can, in turn,
103+
use other suspending functions, like `delay` in this example, to _suspend_ execution of a coroutine.
104+
105+
```kotlin
106+
fun main(args: Array<String>) = runBlocking<Unit> {
107+
val job = launch(CommonPool) { doWorld() }
108+
println("Hello,")
109+
job.join()
110+
}
111+
112+
// this is your first suspending function
113+
suspend fun doWorld() {
114+
delay(1000L)
115+
println("World!")
116+
}
117+
```
118+
119+
> You can get full code [here](kotlinx-coroutines-core/src/test/kotlin/examples/example-04.kt)
120+
121+
## Coroutines ARE light-weight
122+
123+
Run the following code:
124+
125+
```kotlin
126+
fun main(args: Array<String>) = runBlocking<Unit> {
127+
val jobs = List(100_000) { // create a lot of coroutines and list their jobs
128+
launch(CommonPool) {
129+
delay(1000L)
130+
print(".")
131+
}
132+
}
133+
jobs.forEach { it.join() } // wait for all jobs to complete
134+
}
135+
```
136+
137+
> You can get full code [here](kotlinx-coroutines-core/src/test/kotlin/examples/example-05.kt)
138+
139+
It starts 100K coroutines and, after a second, each coroutine prints a dot.
140+
Now, try that with threads. What would happen? (Most likely your code will produce some sort of out-of-memory error)
141+
142+
## Coroutines are like daemon threads
143+
144+
The following code launches a long-running coroutine that prints "I'm sleeping" twice a second and then
145+
returns from the main thread after some delay:
146+
147+
```kotlin
148+
fun main(args: Array<String>) = runBlocking<Unit> {
149+
launch(CommonPool) {
150+
repeat(1000) { i ->
151+
println("I'm sleeping $i ...")
152+
delay(500L)
153+
}
154+
}
155+
delay(1300L) // just quit after delay
156+
}
157+
```
158+
159+
> You can get full code [here](kotlinx-coroutines-core/src/test/kotlin/examples/example-06.kt)
160+
161+
You can run and see that it prints three lines and terminates:
162+
163+
```
164+
I'm sleeping 0 ...
165+
I'm sleeping 1 ...
166+
I'm sleeping 2 ...
167+
```
168+
169+
Active coroutines do not keep the process alive. They are like daemon threads.
170+
171+
## Cancelling coroutine execution
172+
173+
In small application the return from "main" method might sound like a good idea to get all coroutines
174+
implicitly terminated. In a larger, long-running application, you need finer-grained control.
175+
The `launch` function returns a `Job` that can be used to cancel running coroutine:
176+
177+
```kotlin
178+
fun main(args: Array<String>) = runBlocking<Unit> {
179+
val job = launch(CommonPool) {
180+
repeat(1000) { i ->
181+
println("I'm sleeping $i ...")
182+
delay(500L)
183+
}
184+
}
185+
delay(1300L) // delay a bit
186+
println("I'm tired of waiting!")
187+
job.cancel() // cancels the job
188+
delay(1300L) // delay a bit to ensure it was cancelled indeed
189+
println("Now I can quit.")
190+
}
191+
```
192+
193+
> You can get full code [here](kotlinx-coroutines-core/src/test/kotlin/examples/example-07.kt)
194+
195+
## Cancellation is cooperative
196+
197+
Coroutine cancellation is _cooperative_. A coroutine code has to cooperate be cancellable.
198+
All the suspending functions in `kotlinx.coroutines` are _cancellable_. They check for cancellation of
199+
coroutine and throw `CancellationException` when cancelled. However, if a coroutine is working in
200+
a computation and does not check for cancellation, then it cannot be cancelled, like the following
201+
example shows:
202+
203+
```kotlin
204+
fun main(args: Array<String>) = runBlocking<Unit> {
205+
val job = launch(CommonPool) {
206+
var nextPrintTime = 0L
207+
var i = 0
208+
while (true) { // computation loop
209+
val currentTime = System.currentTimeMillis()
210+
if (currentTime >= nextPrintTime) {
211+
println("I'm sleeping ${i++} ...")
212+
nextPrintTime = currentTime + 500L
213+
}
214+
}
215+
}
216+
delay(1300L) // delay a bit
217+
println("I'm tired of waiting!")
218+
job.cancel() // cancels the job
219+
delay(1300L) // delay a bit to see if it was cancelled....
220+
println("Now I can quit.")
221+
}
222+
```
223+
224+
> You can get full code [here](kotlinx-coroutines-core/src/test/kotlin/examples/example-08.kt)
225+
226+
Run it to see that it continues to print "I'm sleeping" even after cancellation.
227+
228+
## Making computation code cancellable
229+
230+
There are two approaches to making computation code cancellable. The first one is to periodically
231+
invoke any suspending function. There is a `yield` function that is a good choice for that purpose.
232+
The other one is to explicitly check the cancellation status. The following example demonstrates
233+
the later approach.
234+
235+
Replace `while (true)` in the previous example with `while (isActive)` and rerun it.
236+
237+
> You can get full code [here](kotlinx-coroutines-core/src/test/kotlin/examples/example-09.kt)
238+
239+
As you can see, now this loop can be cancelled. `isActive` is a property that is available inside
240+
the code of coroutines via `CoroutineScope` object.
241+
242+

kotlinx-coroutines-core/src/test/kotlin/examples/tutorial-example-1.kt renamed to kotlinx-coroutines-core/src/test/kotlin/examples/example-01.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
package examples
22

3-
import kotlinx.coroutines.experimental.Here
3+
import kotlinx.coroutines.experimental.CommonPool
44
import kotlinx.coroutines.experimental.delay
55
import kotlinx.coroutines.experimental.launch
66

77
fun main(args: Array<String>) {
8-
launch(Here) { // create new coroutine without an explicit threading policy
8+
launch(CommonPool) { // create new coroutine in common thread pool
99
delay(1000L) // non-blocking delay for 1 second (default time unit is ms)
1010
println("World!") // print after delay
1111
}

kotlinx-coroutines-core/src/test/kotlin/examples/tutorial-example-2.kt renamed to kotlinx-coroutines-core/src/test/kotlin/examples/example-02.kt

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
package examples
22

3-
import kotlinx.coroutines.experimental.Here
3+
import kotlinx.coroutines.experimental.CommonPool
44
import kotlinx.coroutines.experimental.delay
55
import kotlinx.coroutines.experimental.launch
66
import kotlinx.coroutines.experimental.runBlocking
77

8-
fun main(args: Array<String>) = runBlocking { // start main coroutine
9-
launch(Here) { // create new coroutine without an explicit threading policy
8+
fun main(args: Array<String>) = runBlocking<Unit> { // start main coroutine
9+
launch(CommonPool) { // create new coroutine in common thread pool
1010
delay(1000L)
1111
println("World!")
1212
}

kotlinx-coroutines-core/src/test/kotlin/examples/tutorial-example-3.kt renamed to kotlinx-coroutines-core/src/test/kotlin/examples/example-03.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,8 @@ package examples
22

33
import kotlinx.coroutines.experimental.*
44

5-
fun main(args: Array<String>) = runBlocking {
6-
val job = launch(Here) { // create new coroutine and keep a reference to its Job
5+
fun main(args: Array<String>) = runBlocking<Unit> {
6+
val job = launch(CommonPool) { // create new coroutine and keep a reference to its Job
77
delay(1000L)
88
println("World!")
99
}

kotlinx-coroutines-core/src/test/kotlin/examples/tutorial-example-4.kt renamed to kotlinx-coroutines-core/src/test/kotlin/examples/example-04.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,8 @@ package examples
22

33
import kotlinx.coroutines.experimental.*
44

5-
fun main(args: Array<String>) = runBlocking {
6-
val job = launch(Here) { doWorld() }
5+
fun main(args: Array<String>) = runBlocking<Unit> {
6+
val job = launch(CommonPool) { doWorld() }
77
println("Hello,")
88
job.join()
99
}

kotlinx-coroutines-core/src/test/kotlin/examples/tutorial-example-5.kt renamed to kotlinx-coroutines-core/src/test/kotlin/examples/example-05.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,9 @@ package examples
22

33
import kotlinx.coroutines.experimental.*
44

5-
fun main(args: Array<String>) = runBlocking {
5+
fun main(args: Array<String>) = runBlocking<Unit> {
66
val jobs = List(100_000) { // create a lot of coroutines and list their jobs
7-
launch(Here) {
7+
launch(CommonPool) {
88
delay(1000L)
99
print(".")
1010
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
package examples
2+
3+
import kotlinx.coroutines.experimental.CommonPool
4+
import kotlinx.coroutines.experimental.delay
5+
import kotlinx.coroutines.experimental.launch
6+
import kotlinx.coroutines.experimental.runBlocking
7+
8+
fun main(args: Array<String>) = runBlocking<Unit> {
9+
launch(CommonPool) {
10+
repeat(1000) { i ->
11+
println("I'm sleeping $i ...")
12+
delay(500L)
13+
}
14+
}
15+
delay(1300L) // just quit after delay
16+
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
package examples
2+
3+
import kotlinx.coroutines.experimental.CommonPool
4+
import kotlinx.coroutines.experimental.delay
5+
import kotlinx.coroutines.experimental.launch
6+
import kotlinx.coroutines.experimental.runBlocking
7+
8+
fun main(args: Array<String>) = runBlocking<Unit> {
9+
val job = launch(CommonPool) {
10+
repeat(1000) { i ->
11+
println("I'm sleeping $i ...")
12+
delay(500L)
13+
}
14+
}
15+
delay(1300L) // delay a bit
16+
println("I'm tired of waiting!")
17+
job.cancel() // cancels the job
18+
delay(1300L) // delay a bit to ensure it was cancelled indeed
19+
println("Now I can quit.")
20+
}

0 commit comments

Comments
 (0)