blob: e309d793124e4eb759066696d28b5c79d633dd75 [file] [log] [blame] [view]
hadihariri7db55532018-09-15 10:35:08 +02001<!--- INCLUDE .*/example-([a-z]+)-([0-9a-z]+)\.kt
2/*
3 * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
4 */
5
6// This file was automatically generated from coroutines-guide.md by Knit tool. Do not edit.
7package kotlinx.coroutines.experimental.guide.$$1$$2
8
9import kotlinx.coroutines.experimental.*
10import kotlinx.coroutines.experimental.channels.*
11import kotlinx.coroutines.experimental.selects.*
12-->
13<!--- KNIT ../core/kotlinx-coroutines-core/test/guide/.*\.kt -->
14<!--- TEST_OUT ../core/kotlinx-coroutines-core/test/guide/test/SelectGuideTest.kt
15// This file was automatically generated from coroutines-guide.md by Knit tool. Do not edit.
16package kotlinx.coroutines.experimental.guide.test
17
18import org.junit.Test
19
20class SelectGuideTest {
21-->
22
23
24## Table of contents
25
26<!--- TOC -->
27
28* [Select expression (experimental)](#select-expression-(experimental))
29 * [Selecting from channels](#selecting-from-channels)
30 * [Selecting on close](#selecting-on-close)
31 * [Selecting to send](#selecting-to-send)
32 * [Selecting deferred values](#selecting-deferred-values)
33 * [Switch over a channel of deferred values](#switch-over-a-channel-of-deferred-values)
34
35<!--- END_TOC -->
36
37
38
39## Select expression (experimental)
40
41Select expression makes it possible to await multiple suspending functions simultaneously and _select_
42the first one that becomes available.
43
44> Select expressions are an experimental feature of `kotlinx.coroutines`. Their API is expected to
45evolve in the upcoming updates of the `kotlinx.coroutines` library with potentially
46breaking changes.
47
48### Selecting from channels
49
50Let us have two producers of strings: `fizz` and `buzz`. The `fizz` produces "Fizz" string every 300 ms:
51
52<!--- INCLUDE
53import kotlinx.coroutines.experimental.*
54import kotlin.coroutines.experimental.*
55-->
56
57```kotlin
58fun CoroutineScope.fizz() = produce<String> {
59 while (true) { // sends "Fizz" every 300 ms
60 delay(300)
61 send("Fizz")
62 }
63}
64```
65
66And the `buzz` produces "Buzz!" string every 500 ms:
67
68```kotlin
69fun CoroutineScope.buzz() = produce<String> {
70 while (true) { // sends "Buzz!" every 500 ms
71 delay(500)
72 send("Buzz!")
73 }
74}
75```
76
77Using [receive][ReceiveChannel.receive] suspending function we can receive _either_ from one channel or the
78other. But [select] expression allows us to receive from _both_ simultaneously using its
79[onReceive][ReceiveChannel.onReceive] clauses:
80
81```kotlin
82suspend fun selectFizzBuzz(fizz: ReceiveChannel<String>, buzz: ReceiveChannel<String>) {
83 select<Unit> { // <Unit> means that this select expression does not produce any result
84 fizz.onReceive { value -> // this is the first select clause
85 println("fizz -> '$value'")
86 }
87 buzz.onReceive { value -> // this is the second select clause
88 println("buzz -> '$value'")
89 }
90 }
91}
92```
93
94Let us run it all seven times:
95
96```kotlin
97fun main(args: Array<String>) = runBlocking<Unit> {
98 val fizz = fizz()
99 val buzz = buzz()
100 repeat(7) {
101 selectFizzBuzz(fizz, buzz)
102 }
103 coroutineContext.cancelChildren() // cancel fizz & buzz coroutines
104}
105```
106
107> You can get full code [here](../core/kotlinx-coroutines-core/test/guide/example-select-01.kt)
108
109The result of this code is:
110
111```text
112fizz -> 'Fizz'
113buzz -> 'Buzz!'
114fizz -> 'Fizz'
115fizz -> 'Fizz'
116buzz -> 'Buzz!'
117fizz -> 'Fizz'
118buzz -> 'Buzz!'
119```
120
121<!--- TEST -->
122
123### Selecting on close
124
125The [onReceive][ReceiveChannel.onReceive] clause in `select` fails when the channel is closed causing the corresponding
126`select` to throw an exception. We can use [onReceiveOrNull][ReceiveChannel.onReceiveOrNull] clause to perform a
127specific action when the channel is closed. The following example also shows that `select` is an expression that returns
128the result of its selected clause:
129
130<!--- INCLUDE
131import kotlin.coroutines.experimental.*
132-->
133
134```kotlin
135suspend fun selectAorB(a: ReceiveChannel<String>, b: ReceiveChannel<String>): String =
136 select<String> {
137 a.onReceiveOrNull { value ->
138 if (value == null)
139 "Channel 'a' is closed"
140 else
141 "a -> '$value'"
142 }
143 b.onReceiveOrNull { value ->
144 if (value == null)
145 "Channel 'b' is closed"
146 else
147 "b -> '$value'"
148 }
149 }
150```
151
152Let's use it with channel `a` that produces "Hello" string four times and
153channel `b` that produces "World" four times:
154
155```kotlin
156fun main(args: Array<String>) = runBlocking<Unit> {
157 val a = produce<String> {
158 repeat(4) { send("Hello $it") }
159 }
160 val b = produce<String> {
161 repeat(4) { send("World $it") }
162 }
163 repeat(8) { // print first eight results
164 println(selectAorB(a, b))
165 }
166 coroutineContext.cancelChildren()
167}
168```
169
170> You can get full code [here](../core/kotlinx-coroutines-core/test/guide/example-select-02.kt)
171
172The result of this code is quite interesting, so we'll analyze it in mode detail:
173
174```text
175a -> 'Hello 0'
176a -> 'Hello 1'
177b -> 'World 0'
178a -> 'Hello 2'
179a -> 'Hello 3'
180b -> 'World 1'
181Channel 'a' is closed
182Channel 'a' is closed
183```
184
185<!--- TEST -->
186
187There are couple of observations to make out of it.
188
189First of all, `select` is _biased_ to the first clause. When several clauses are selectable at the same time,
190the first one among them gets selected. Here, both channels are constantly producing strings, so `a` channel,
191being the first clause in select, wins. However, because we are using unbuffered channel, the `a` gets suspended from
192time to time on its [send][SendChannel.send] invocation and gives a chance for `b` to send, too.
193
194The second observation, is that [onReceiveOrNull][ReceiveChannel.onReceiveOrNull] gets immediately selected when the
195channel is already closed.
196
197### Selecting to send
198
199Select expression has [onSend][SendChannel.onSend] clause that can be used for a great good in combination
200with a biased nature of selection.
201
202Let us write an example of producer of integers that sends its values to a `side` channel when
203the consumers on its primary channel cannot keep up with it:
204
205<!--- INCLUDE
206import kotlin.coroutines.experimental.*
207-->
208
209```kotlin
210fun CoroutineScope.produceNumbers(side: SendChannel<Int>) = produce<Int> {
211 for (num in 1..10) { // produce 10 numbers from 1 to 10
212 delay(100) // every 100 ms
213 select<Unit> {
214 onSend(num) {} // Send to the primary channel
215 side.onSend(num) {} // or to the side channel
216 }
217 }
218}
219```
220
221Consumer is going to be quite slow, taking 250 ms to process each number:
222
223```kotlin
224fun main(args: Array<String>) = runBlocking<Unit> {
225 val side = Channel<Int>() // allocate side channel
226 launch { // this is a very fast consumer for the side channel
227 side.consumeEach { println("Side channel has $it") }
228 }
229 produceNumbers(side).consumeEach {
230 println("Consuming $it")
231 delay(250) // let us digest the consumed number properly, do not hurry
232 }
233 println("Done consuming")
234 coroutineContext.cancelChildren()
235}
236```
237
238> You can get full code [here](../core/kotlinx-coroutines-core/test/guide/example-select-03.kt)
239
240So let us see what happens:
241
242```text
243Consuming 1
244Side channel has 2
245Side channel has 3
246Consuming 4
247Side channel has 5
248Side channel has 6
249Consuming 7
250Side channel has 8
251Side channel has 9
252Consuming 10
253Done consuming
254```
255
256<!--- TEST -->
257
258### Selecting deferred values
259
260Deferred values can be selected using [onAwait][Deferred.onAwait] clause.
261Let us start with an async function that returns a deferred string value after
262a random delay:
263
264<!--- INCLUDE .*/example-select-04.kt
265import java.util.*
266-->
267
268```kotlin
269fun CoroutineScope.asyncString(time: Int) = async {
270 delay(time.toLong())
271 "Waited for $time ms"
272}
273```
274
275Let us start a dozen of them with a random delay.
276
277```kotlin
278fun CoroutineScope.asyncStringsList(): List<Deferred<String>> {
279 val random = Random(3)
280 return List(12) { asyncString(random.nextInt(1000)) }
281}
282```
283
284Now the main function awaits for the first of them to complete and counts the number of deferred values
285that are still active. Note, that we've used here the fact that `select` expression is a Kotlin DSL,
286so we can provide clauses for it using an arbitrary code. In this case we iterate over a list
287of deferred values to provide `onAwait` clause for each deferred value.
288
289```kotlin
290fun main(args: Array<String>) = runBlocking<Unit> {
291 val list = asyncStringsList()
292 val result = select<String> {
293 list.withIndex().forEach { (index, deferred) ->
294 deferred.onAwait { answer ->
295 "Deferred $index produced answer '$answer'"
296 }
297 }
298 }
299 println(result)
300 val countActive = list.count { it.isActive }
301 println("$countActive coroutines are still active")
302}
303```
304
305> You can get full code [here](../core/kotlinx-coroutines-core/test/guide/example-select-04.kt)
306
307The output is:
308
309```text
310Deferred 4 produced answer 'Waited for 128 ms'
31111 coroutines are still active
312```
313
314<!--- TEST -->
315
316### Switch over a channel of deferred values
317
318Let us write a channel producer function that consumes a channel of deferred string values, waits for each received
319deferred value, but only until the next deferred value comes over or the channel is closed. This example puts together
320[onReceiveOrNull][ReceiveChannel.onReceiveOrNull] and [onAwait][Deferred.onAwait] clauses in the same `select`:
321
322<!--- INCLUDE
323import kotlin.coroutines.experimental.*
324-->
325
326```kotlin
327fun CoroutineScope.switchMapDeferreds(input: ReceiveChannel<Deferred<String>>) = produce<String> {
328 var current = input.receive() // start with first received deferred value
329 while (isActive) { // loop while not cancelled/closed
330 val next = select<Deferred<String>?> { // return next deferred value from this select or null
331 input.onReceiveOrNull { update ->
332 update // replaces next value to wait
333 }
334 current.onAwait { value ->
335 send(value) // send value that current deferred has produced
336 input.receiveOrNull() // and use the next deferred from the input channel
337 }
338 }
339 if (next == null) {
340 println("Channel was closed")
341 break // out of loop
342 } else {
343 current = next
344 }
345 }
346}
347```
348
349To test it, we'll use a simple async function that resolves to a specified string after a specified time:
350
351```kotlin
352fun CoroutineScope.asyncString(str: String, time: Long) = async {
353 delay(time)
354 str
355}
356```
357
358The main function just launches a coroutine to print results of `switchMapDeferreds` and sends some test
359data to it:
360
361```kotlin
362fun main(args: Array<String>) = runBlocking<Unit> {
363 val chan = Channel<Deferred<String>>() // the channel for test
364 launch { // launch printing coroutine
365 for (s in switchMapDeferreds(chan))
366 println(s) // print each received string
367 }
368 chan.send(asyncString("BEGIN", 100))
369 delay(200) // enough time for "BEGIN" to be produced
370 chan.send(asyncString("Slow", 500))
371 delay(100) // not enough time to produce slow
372 chan.send(asyncString("Replace", 100))
373 delay(500) // give it time before the last one
374 chan.send(asyncString("END", 500))
375 delay(1000) // give it time to process
376 chan.close() // close the channel ...
377 delay(500) // and wait some time to let it finish
378}
379```
380
381> You can get full code [here](../core/kotlinx-coroutines-core/test/guide/example-select-05.kt)
382
383The result of this code:
384
385```text
386BEGIN
387Replace
388END
389Channel was closed
390```
391
392<!--- TEST -->
393
394<!--- MODULE kotlinx-coroutines-core -->
395<!--- INDEX kotlinx.coroutines.experimental.selects -->
396[select]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.experimental.selects/select.html
397<!--- END -->