blob: 4f70671b2f609cdecb6c6a7e25607a3780f03357 [file] [log] [blame] [view]
Roman Elizarov7deefb82017-01-31 10:33:17 +03001# Guide to kotlinx.coroutines by example
2
3This is a short guide on core features of `kotlinx.coroutines` with a series of examples.
4
5## Your first coroutine
6
7Run the following code:
8
9```kotlin
10fun 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
22Run this code:
23
24```
25Hello,
26World!
27```
28
29Essentially, 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
32If you start by replacing `launch(CommonPool)` by `thread`, the compiler produces the following error:
33
34```
35Error: Kotlin: Suspend functions are only allowed to be called from a coroutine or another suspend function
36```
37
38That is because `delay` is a special _suspending function_ that does not block a thread, but _suspends_
39coroutine and it can be only used from a coroutine.
40
41## Bridging blocking and non-blocking worlds
42
43The first example mixes _non-blocking_ `delay(...)` and _blocking_ `Thread.sleep(...)` in the same
44code of `main` function. It is easy to get lost. Let's cleanly separate blocking and non-blocking
45worlds by using `runBlocking { ... }`:
46
47```kotlin
48fun 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
60The 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.
63The regular code outside of `runBlocking` _blocks_, until the coroutine inside `runBlocking` is active.
64
65This is also a way to write unit-tests for suspending functions:
66
67```kotlin
68class 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
78Delaying for a time while a _child_ coroutine is working is not a good approach. Let's explicitly
79wait (in a non-blocking way) until the other coroutine that we have launched is complete:
80
81```kotlin
82fun 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
94Now the result is still the same, but the code of the main coroutine is not tied to the duration of
95the child coroutine in any way. Much better.
96
97## Extract function refactoring
98
99Let's extract the block of code inside `launch(CommonPool} { ... }` into a separate function. When you
100perform "Extract function" refactoring on this code you get a new function with `suspend` modifier.
101That is your first _suspending function_. Suspending functions can be used inside coroutines
102just like regular functions, but their additional feature is that they can, in turn,
103use other suspending functions, like `delay` in this example, to _suspend_ execution of a coroutine.
104
105```kotlin
106fun 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
113suspend 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
123Run the following code:
124
125```kotlin
126fun 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
139It starts 100K coroutines and, after a second, each coroutine prints a dot.
140Now, 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
144The following code launches a long-running coroutine that prints "I'm sleeping" twice a second and then
145returns from the main thread after some delay:
146
147```kotlin
148fun 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
161You can run and see that it prints three lines and terminates:
162
163```
164I'm sleeping 0 ...
165I'm sleeping 1 ...
166I'm sleeping 2 ...
167```
168
169Active coroutines do not keep the process alive. They are like daemon threads.
170
171## Cancelling coroutine execution
172
173In small application the return from "main" method might sound like a good idea to get all coroutines
174implicitly terminated. In a larger, long-running application, you need finer-grained control.
175The `launch` function returns a `Job` that can be used to cancel running coroutine:
176
177```kotlin
178fun 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
197Coroutine cancellation is _cooperative_. A coroutine code has to cooperate be cancellable.
198All the suspending functions in `kotlinx.coroutines` are _cancellable_. They check for cancellation of
199coroutine and throw `CancellationException` when cancelled. However, if a coroutine is working in
200a computation and does not check for cancellation, then it cannot be cancelled, like the following
201example shows:
202
203```kotlin
204fun 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
226Run it to see that it continues to print "I'm sleeping" even after cancellation.
227
228## Making computation code cancellable
229
230There are two approaches to making computation code cancellable. The first one is to periodically
231invoke any suspending function. There is a `yield` function that is a good choice for that purpose.
232The other one is to explicitly check the cancellation status. The following example demonstrates
233the later approach.
234
235Replace `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
239As you can see, now this loop can be cancelled. `isActive` is a property that is available inside
240the code of coroutines via `CoroutineScope` object.
241
242