hadihariri | 7db5553 | 2018-09-15 10:35:08 +0200 | [diff] [blame] | 1 | <!--- 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. |
Roman Elizarov | 0950dfa | 2018-07-13 10:33:25 +0300 | [diff] [blame] | 7 | package kotlinx.coroutines.guide.$$1$$2 |
hadihariri | 7db5553 | 2018-09-15 10:35:08 +0200 | [diff] [blame] | 8 | --> |
| 9 | <!--- KNIT ../core/kotlinx-coroutines-core/test/guide/.*\.kt --> |
| 10 | <!--- TEST_OUT ../core/kotlinx-coroutines-core/test/guide/test/SelectGuideTest.kt |
| 11 | // This file was automatically generated from coroutines-guide.md by Knit tool. Do not edit. |
Roman Elizarov | 0950dfa | 2018-07-13 10:33:25 +0300 | [diff] [blame] | 12 | package kotlinx.coroutines.guide.test |
hadihariri | 7db5553 | 2018-09-15 10:35:08 +0200 | [diff] [blame] | 13 | |
| 14 | import org.junit.Test |
| 15 | |
| 16 | class SelectGuideTest { |
| 17 | --> |
| 18 | |
| 19 | |
| 20 | ## Table of contents |
| 21 | |
| 22 | <!--- TOC --> |
| 23 | |
Vsevolod Tolstopyatov | b590aa3 | 2018-09-27 18:34:05 +0300 | [diff] [blame] | 24 | * [Select expression (experimental)](#select-expression-experimental) |
hadihariri | 7db5553 | 2018-09-15 10:35:08 +0200 | [diff] [blame] | 25 | * [Selecting from channels](#selecting-from-channels) |
| 26 | * [Selecting on close](#selecting-on-close) |
| 27 | * [Selecting to send](#selecting-to-send) |
| 28 | * [Selecting deferred values](#selecting-deferred-values) |
| 29 | * [Switch over a channel of deferred values](#switch-over-a-channel-of-deferred-values) |
| 30 | |
| 31 | <!--- END_TOC --> |
| 32 | |
| 33 | |
| 34 | |
| 35 | ## Select expression (experimental) |
| 36 | |
| 37 | Select expression makes it possible to await multiple suspending functions simultaneously and _select_ |
| 38 | the first one that becomes available. |
| 39 | |
| 40 | > Select expressions are an experimental feature of `kotlinx.coroutines`. Their API is expected to |
| 41 | evolve in the upcoming updates of the `kotlinx.coroutines` library with potentially |
| 42 | breaking changes. |
| 43 | |
| 44 | ### Selecting from channels |
| 45 | |
| 46 | Let us have two producers of strings: `fizz` and `buzz`. The `fizz` produces "Fizz" string every 300 ms: |
| 47 | |
Alexander Prendota | cbeef10 | 2018-09-27 18:42:04 +0300 | [diff] [blame] | 48 | <div class="sample" markdown="1" theme="idea" data-highlight-only> |
| 49 | |
hadihariri | 7db5553 | 2018-09-15 10:35:08 +0200 | [diff] [blame] | 50 | ```kotlin |
| 51 | fun CoroutineScope.fizz() = produce<String> { |
| 52 | while (true) { // sends "Fizz" every 300 ms |
| 53 | delay(300) |
| 54 | send("Fizz") |
| 55 | } |
| 56 | } |
| 57 | ``` |
| 58 | |
Alexander Prendota | cbeef10 | 2018-09-27 18:42:04 +0300 | [diff] [blame] | 59 | </div> |
| 60 | |
hadihariri | 7db5553 | 2018-09-15 10:35:08 +0200 | [diff] [blame] | 61 | And the `buzz` produces "Buzz!" string every 500 ms: |
| 62 | |
Alexander Prendota | cbeef10 | 2018-09-27 18:42:04 +0300 | [diff] [blame] | 63 | <div class="sample" markdown="1" theme="idea" data-highlight-only> |
| 64 | |
hadihariri | 7db5553 | 2018-09-15 10:35:08 +0200 | [diff] [blame] | 65 | ```kotlin |
| 66 | fun CoroutineScope.buzz() = produce<String> { |
| 67 | while (true) { // sends "Buzz!" every 500 ms |
| 68 | delay(500) |
| 69 | send("Buzz!") |
| 70 | } |
| 71 | } |
| 72 | ``` |
| 73 | |
Alexander Prendota | cbeef10 | 2018-09-27 18:42:04 +0300 | [diff] [blame] | 74 | </div> |
| 75 | |
hadihariri | 7db5553 | 2018-09-15 10:35:08 +0200 | [diff] [blame] | 76 | Using [receive][ReceiveChannel.receive] suspending function we can receive _either_ from one channel or the |
| 77 | other. But [select] expression allows us to receive from _both_ simultaneously using its |
| 78 | [onReceive][ReceiveChannel.onReceive] clauses: |
| 79 | |
Alexander Prendota | cbeef10 | 2018-09-27 18:42:04 +0300 | [diff] [blame] | 80 | <div class="sample" markdown="1" theme="idea" data-highlight-only> |
| 81 | |
hadihariri | 7db5553 | 2018-09-15 10:35:08 +0200 | [diff] [blame] | 82 | ```kotlin |
| 83 | suspend fun selectFizzBuzz(fizz: ReceiveChannel<String>, buzz: ReceiveChannel<String>) { |
| 84 | select<Unit> { // <Unit> means that this select expression does not produce any result |
| 85 | fizz.onReceive { value -> // this is the first select clause |
| 86 | println("fizz -> '$value'") |
| 87 | } |
| 88 | buzz.onReceive { value -> // this is the second select clause |
| 89 | println("buzz -> '$value'") |
| 90 | } |
| 91 | } |
| 92 | } |
| 93 | ``` |
| 94 | |
Alexander Prendota | cbeef10 | 2018-09-27 18:42:04 +0300 | [diff] [blame] | 95 | </div> |
| 96 | |
hadihariri | 7db5553 | 2018-09-15 10:35:08 +0200 | [diff] [blame] | 97 | Let us run it all seven times: |
| 98 | |
Prendota | 0eee3c3 | 2018-10-22 12:52:56 +0300 | [diff] [blame] | 99 | <!--- CLEAR --> |
| 100 | |
| 101 | <div class="sample" markdown="1" theme="idea" data-min-compiler-version="1.3"> |
Alexander Prendota | cbeef10 | 2018-09-27 18:42:04 +0300 | [diff] [blame] | 102 | |
hadihariri | 7db5553 | 2018-09-15 10:35:08 +0200 | [diff] [blame] | 103 | ```kotlin |
Prendota | 0eee3c3 | 2018-10-22 12:52:56 +0300 | [diff] [blame] | 104 | import kotlinx.coroutines.* |
| 105 | import kotlinx.coroutines.channels.* |
| 106 | import kotlinx.coroutines.selects.* |
| 107 | |
| 108 | fun CoroutineScope.fizz() = produce<String> { |
| 109 | while (true) { // sends "Fizz" every 300 ms |
| 110 | delay(300) |
| 111 | send("Fizz") |
| 112 | } |
| 113 | } |
| 114 | |
| 115 | fun CoroutineScope.buzz() = produce<String> { |
| 116 | while (true) { // sends "Buzz!" every 500 ms |
| 117 | delay(500) |
| 118 | send("Buzz!") |
| 119 | } |
| 120 | } |
| 121 | |
| 122 | suspend fun selectFizzBuzz(fizz: ReceiveChannel<String>, buzz: ReceiveChannel<String>) { |
| 123 | select<Unit> { // <Unit> means that this select expression does not produce any result |
| 124 | fizz.onReceive { value -> // this is the first select clause |
| 125 | println("fizz -> '$value'") |
| 126 | } |
| 127 | buzz.onReceive { value -> // this is the second select clause |
| 128 | println("buzz -> '$value'") |
| 129 | } |
| 130 | } |
| 131 | } |
| 132 | |
Prendota | 65e6c8c | 2018-10-17 11:51:08 +0300 | [diff] [blame] | 133 | fun main() = runBlocking<Unit> { |
Prendota | 0eee3c3 | 2018-10-22 12:52:56 +0300 | [diff] [blame] | 134 | //sampleStart |
hadihariri | 7db5553 | 2018-09-15 10:35:08 +0200 | [diff] [blame] | 135 | val fizz = fizz() |
| 136 | val buzz = buzz() |
| 137 | repeat(7) { |
| 138 | selectFizzBuzz(fizz, buzz) |
| 139 | } |
Prendota | 0eee3c3 | 2018-10-22 12:52:56 +0300 | [diff] [blame] | 140 | coroutineContext.cancelChildren() // cancel fizz & buzz coroutines |
| 141 | //sampleEnd |
hadihariri | 7db5553 | 2018-09-15 10:35:08 +0200 | [diff] [blame] | 142 | } |
| 143 | ``` |
| 144 | |
Alexander Prendota | cbeef10 | 2018-09-27 18:42:04 +0300 | [diff] [blame] | 145 | </div> |
| 146 | |
hadihariri | 7db5553 | 2018-09-15 10:35:08 +0200 | [diff] [blame] | 147 | > You can get full code [here](../core/kotlinx-coroutines-core/test/guide/example-select-01.kt) |
| 148 | |
| 149 | The result of this code is: |
| 150 | |
| 151 | ```text |
| 152 | fizz -> 'Fizz' |
| 153 | buzz -> 'Buzz!' |
| 154 | fizz -> 'Fizz' |
| 155 | fizz -> 'Fizz' |
| 156 | buzz -> 'Buzz!' |
| 157 | fizz -> 'Fizz' |
| 158 | buzz -> 'Buzz!' |
| 159 | ``` |
| 160 | |
| 161 | <!--- TEST --> |
| 162 | |
| 163 | ### Selecting on close |
| 164 | |
| 165 | The [onReceive][ReceiveChannel.onReceive] clause in `select` fails when the channel is closed causing the corresponding |
| 166 | `select` to throw an exception. We can use [onReceiveOrNull][ReceiveChannel.onReceiveOrNull] clause to perform a |
| 167 | specific action when the channel is closed. The following example also shows that `select` is an expression that returns |
| 168 | the result of its selected clause: |
| 169 | |
Alexander Prendota | cbeef10 | 2018-09-27 18:42:04 +0300 | [diff] [blame] | 170 | <div class="sample" markdown="1" theme="idea" data-highlight-only> |
| 171 | |
hadihariri | 7db5553 | 2018-09-15 10:35:08 +0200 | [diff] [blame] | 172 | ```kotlin |
| 173 | suspend fun selectAorB(a: ReceiveChannel<String>, b: ReceiveChannel<String>): String = |
| 174 | select<String> { |
| 175 | a.onReceiveOrNull { value -> |
| 176 | if (value == null) |
| 177 | "Channel 'a' is closed" |
| 178 | else |
| 179 | "a -> '$value'" |
| 180 | } |
| 181 | b.onReceiveOrNull { value -> |
| 182 | if (value == null) |
| 183 | "Channel 'b' is closed" |
| 184 | else |
| 185 | "b -> '$value'" |
| 186 | } |
| 187 | } |
| 188 | ``` |
| 189 | |
Alexander Prendota | cbeef10 | 2018-09-27 18:42:04 +0300 | [diff] [blame] | 190 | </div> |
| 191 | |
hadihariri | 7db5553 | 2018-09-15 10:35:08 +0200 | [diff] [blame] | 192 | Let's use it with channel `a` that produces "Hello" string four times and |
| 193 | channel `b` that produces "World" four times: |
| 194 | |
Prendota | 0eee3c3 | 2018-10-22 12:52:56 +0300 | [diff] [blame] | 195 | <!--- CLEAR --> |
| 196 | |
| 197 | <div class="sample" markdown="1" theme="idea" data-min-compiler-version="1.3"> |
Alexander Prendota | cbeef10 | 2018-09-27 18:42:04 +0300 | [diff] [blame] | 198 | |
hadihariri | 7db5553 | 2018-09-15 10:35:08 +0200 | [diff] [blame] | 199 | ```kotlin |
Prendota | 0eee3c3 | 2018-10-22 12:52:56 +0300 | [diff] [blame] | 200 | import kotlinx.coroutines.* |
| 201 | import kotlinx.coroutines.channels.* |
| 202 | import kotlinx.coroutines.selects.* |
| 203 | |
| 204 | suspend fun selectAorB(a: ReceiveChannel<String>, b: ReceiveChannel<String>): String = |
| 205 | select<String> { |
| 206 | a.onReceiveOrNull { value -> |
| 207 | if (value == null) |
| 208 | "Channel 'a' is closed" |
| 209 | else |
| 210 | "a -> '$value'" |
| 211 | } |
| 212 | b.onReceiveOrNull { value -> |
| 213 | if (value == null) |
| 214 | "Channel 'b' is closed" |
| 215 | else |
| 216 | "b -> '$value'" |
| 217 | } |
| 218 | } |
| 219 | |
Prendota | 65e6c8c | 2018-10-17 11:51:08 +0300 | [diff] [blame] | 220 | fun main() = runBlocking<Unit> { |
Prendota | 0eee3c3 | 2018-10-22 12:52:56 +0300 | [diff] [blame] | 221 | //sampleStart |
hadihariri | 7db5553 | 2018-09-15 10:35:08 +0200 | [diff] [blame] | 222 | val a = produce<String> { |
| 223 | repeat(4) { send("Hello $it") } |
| 224 | } |
| 225 | val b = produce<String> { |
| 226 | repeat(4) { send("World $it") } |
| 227 | } |
| 228 | repeat(8) { // print first eight results |
| 229 | println(selectAorB(a, b)) |
| 230 | } |
Prendota | 0eee3c3 | 2018-10-22 12:52:56 +0300 | [diff] [blame] | 231 | coroutineContext.cancelChildren() |
| 232 | //sampleEnd |
| 233 | } |
hadihariri | 7db5553 | 2018-09-15 10:35:08 +0200 | [diff] [blame] | 234 | ``` |
| 235 | |
Alexander Prendota | cbeef10 | 2018-09-27 18:42:04 +0300 | [diff] [blame] | 236 | </div> |
| 237 | |
hadihariri | 7db5553 | 2018-09-15 10:35:08 +0200 | [diff] [blame] | 238 | > You can get full code [here](../core/kotlinx-coroutines-core/test/guide/example-select-02.kt) |
| 239 | |
| 240 | The result of this code is quite interesting, so we'll analyze it in mode detail: |
| 241 | |
| 242 | ```text |
| 243 | a -> 'Hello 0' |
| 244 | a -> 'Hello 1' |
| 245 | b -> 'World 0' |
| 246 | a -> 'Hello 2' |
| 247 | a -> 'Hello 3' |
| 248 | b -> 'World 1' |
| 249 | Channel 'a' is closed |
| 250 | Channel 'a' is closed |
| 251 | ``` |
| 252 | |
| 253 | <!--- TEST --> |
| 254 | |
| 255 | There are couple of observations to make out of it. |
| 256 | |
| 257 | First of all, `select` is _biased_ to the first clause. When several clauses are selectable at the same time, |
| 258 | the first one among them gets selected. Here, both channels are constantly producing strings, so `a` channel, |
| 259 | being the first clause in select, wins. However, because we are using unbuffered channel, the `a` gets suspended from |
| 260 | time to time on its [send][SendChannel.send] invocation and gives a chance for `b` to send, too. |
| 261 | |
| 262 | The second observation, is that [onReceiveOrNull][ReceiveChannel.onReceiveOrNull] gets immediately selected when the |
| 263 | channel is already closed. |
| 264 | |
| 265 | ### Selecting to send |
| 266 | |
| 267 | Select expression has [onSend][SendChannel.onSend] clause that can be used for a great good in combination |
| 268 | with a biased nature of selection. |
| 269 | |
| 270 | Let us write an example of producer of integers that sends its values to a `side` channel when |
| 271 | the consumers on its primary channel cannot keep up with it: |
| 272 | |
Alexander Prendota | cbeef10 | 2018-09-27 18:42:04 +0300 | [diff] [blame] | 273 | <div class="sample" markdown="1" theme="idea" data-highlight-only> |
| 274 | |
hadihariri | 7db5553 | 2018-09-15 10:35:08 +0200 | [diff] [blame] | 275 | ```kotlin |
| 276 | fun CoroutineScope.produceNumbers(side: SendChannel<Int>) = produce<Int> { |
| 277 | for (num in 1..10) { // produce 10 numbers from 1 to 10 |
| 278 | delay(100) // every 100 ms |
| 279 | select<Unit> { |
| 280 | onSend(num) {} // Send to the primary channel |
| 281 | side.onSend(num) {} // or to the side channel |
| 282 | } |
| 283 | } |
| 284 | } |
| 285 | ``` |
| 286 | |
Alexander Prendota | cbeef10 | 2018-09-27 18:42:04 +0300 | [diff] [blame] | 287 | </div> |
| 288 | |
hadihariri | 7db5553 | 2018-09-15 10:35:08 +0200 | [diff] [blame] | 289 | Consumer is going to be quite slow, taking 250 ms to process each number: |
| 290 | |
Prendota | 0eee3c3 | 2018-10-22 12:52:56 +0300 | [diff] [blame] | 291 | <!--- CLEAR --> |
| 292 | |
| 293 | <div class="sample" markdown="1" theme="idea" data-min-compiler-version="1.3"> |
Alexander Prendota | cbeef10 | 2018-09-27 18:42:04 +0300 | [diff] [blame] | 294 | |
hadihariri | 7db5553 | 2018-09-15 10:35:08 +0200 | [diff] [blame] | 295 | ```kotlin |
Prendota | 0eee3c3 | 2018-10-22 12:52:56 +0300 | [diff] [blame] | 296 | import kotlinx.coroutines.* |
| 297 | import kotlinx.coroutines.channels.* |
| 298 | import kotlinx.coroutines.selects.* |
| 299 | |
| 300 | fun CoroutineScope.produceNumbers(side: SendChannel<Int>) = produce<Int> { |
| 301 | for (num in 1..10) { // produce 10 numbers from 1 to 10 |
| 302 | delay(100) // every 100 ms |
| 303 | select<Unit> { |
| 304 | onSend(num) {} // Send to the primary channel |
| 305 | side.onSend(num) {} // or to the side channel |
| 306 | } |
| 307 | } |
| 308 | } |
| 309 | |
Prendota | 65e6c8c | 2018-10-17 11:51:08 +0300 | [diff] [blame] | 310 | fun main() = runBlocking<Unit> { |
Prendota | 0eee3c3 | 2018-10-22 12:52:56 +0300 | [diff] [blame] | 311 | //sampleStart |
hadihariri | 7db5553 | 2018-09-15 10:35:08 +0200 | [diff] [blame] | 312 | val side = Channel<Int>() // allocate side channel |
| 313 | launch { // this is a very fast consumer for the side channel |
| 314 | side.consumeEach { println("Side channel has $it") } |
| 315 | } |
| 316 | produceNumbers(side).consumeEach { |
| 317 | println("Consuming $it") |
| 318 | delay(250) // let us digest the consumed number properly, do not hurry |
| 319 | } |
| 320 | println("Done consuming") |
Prendota | 0eee3c3 | 2018-10-22 12:52:56 +0300 | [diff] [blame] | 321 | coroutineContext.cancelChildren() |
| 322 | //sampleEnd |
hadihariri | 7db5553 | 2018-09-15 10:35:08 +0200 | [diff] [blame] | 323 | } |
Alexander Prendota | cbeef10 | 2018-09-27 18:42:04 +0300 | [diff] [blame] | 324 | ``` |
| 325 | |
| 326 | </div> |
hadihariri | 7db5553 | 2018-09-15 10:35:08 +0200 | [diff] [blame] | 327 | |
| 328 | > You can get full code [here](../core/kotlinx-coroutines-core/test/guide/example-select-03.kt) |
| 329 | |
| 330 | So let us see what happens: |
| 331 | |
| 332 | ```text |
| 333 | Consuming 1 |
| 334 | Side channel has 2 |
| 335 | Side channel has 3 |
| 336 | Consuming 4 |
| 337 | Side channel has 5 |
| 338 | Side channel has 6 |
| 339 | Consuming 7 |
| 340 | Side channel has 8 |
| 341 | Side channel has 9 |
| 342 | Consuming 10 |
| 343 | Done consuming |
| 344 | ``` |
| 345 | |
| 346 | <!--- TEST --> |
| 347 | |
| 348 | ### Selecting deferred values |
| 349 | |
| 350 | Deferred values can be selected using [onAwait][Deferred.onAwait] clause. |
| 351 | Let us start with an async function that returns a deferred string value after |
| 352 | a random delay: |
| 353 | |
Alexander Prendota | cbeef10 | 2018-09-27 18:42:04 +0300 | [diff] [blame] | 354 | <div class="sample" markdown="1" theme="idea" data-highlight-only> |
| 355 | |
hadihariri | 7db5553 | 2018-09-15 10:35:08 +0200 | [diff] [blame] | 356 | ```kotlin |
| 357 | fun CoroutineScope.asyncString(time: Int) = async { |
| 358 | delay(time.toLong()) |
| 359 | "Waited for $time ms" |
| 360 | } |
| 361 | ``` |
| 362 | |
Alexander Prendota | cbeef10 | 2018-09-27 18:42:04 +0300 | [diff] [blame] | 363 | </div> |
| 364 | |
hadihariri | 7db5553 | 2018-09-15 10:35:08 +0200 | [diff] [blame] | 365 | Let us start a dozen of them with a random delay. |
| 366 | |
Alexander Prendota | cbeef10 | 2018-09-27 18:42:04 +0300 | [diff] [blame] | 367 | <div class="sample" markdown="1" theme="idea" data-highlight-only> |
| 368 | |
hadihariri | 7db5553 | 2018-09-15 10:35:08 +0200 | [diff] [blame] | 369 | ```kotlin |
| 370 | fun CoroutineScope.asyncStringsList(): List<Deferred<String>> { |
| 371 | val random = Random(3) |
| 372 | return List(12) { asyncString(random.nextInt(1000)) } |
| 373 | } |
| 374 | ``` |
| 375 | |
Alexander Prendota | cbeef10 | 2018-09-27 18:42:04 +0300 | [diff] [blame] | 376 | </div> |
| 377 | |
hadihariri | 7db5553 | 2018-09-15 10:35:08 +0200 | [diff] [blame] | 378 | Now the main function awaits for the first of them to complete and counts the number of deferred values |
| 379 | that are still active. Note, that we've used here the fact that `select` expression is a Kotlin DSL, |
| 380 | so we can provide clauses for it using an arbitrary code. In this case we iterate over a list |
| 381 | of deferred values to provide `onAwait` clause for each deferred value. |
| 382 | |
Prendota | 0eee3c3 | 2018-10-22 12:52:56 +0300 | [diff] [blame] | 383 | <!--- CLEAR --> |
| 384 | |
| 385 | <div class="sample" markdown="1" theme="idea" data-min-compiler-version="1.3"> |
Alexander Prendota | cbeef10 | 2018-09-27 18:42:04 +0300 | [diff] [blame] | 386 | |
hadihariri | 7db5553 | 2018-09-15 10:35:08 +0200 | [diff] [blame] | 387 | ```kotlin |
Prendota | 0eee3c3 | 2018-10-22 12:52:56 +0300 | [diff] [blame] | 388 | import kotlinx.coroutines.* |
| 389 | import kotlinx.coroutines.selects.* |
| 390 | import java.util.* |
| 391 | |
| 392 | fun CoroutineScope.asyncString(time: Int) = async { |
| 393 | delay(time.toLong()) |
| 394 | "Waited for $time ms" |
| 395 | } |
| 396 | |
| 397 | fun CoroutineScope.asyncStringsList(): List<Deferred<String>> { |
| 398 | val random = Random(3) |
| 399 | return List(12) { asyncString(random.nextInt(1000)) } |
| 400 | } |
| 401 | |
Prendota | 65e6c8c | 2018-10-17 11:51:08 +0300 | [diff] [blame] | 402 | fun main() = runBlocking<Unit> { |
Prendota | 0eee3c3 | 2018-10-22 12:52:56 +0300 | [diff] [blame] | 403 | //sampleStart |
hadihariri | 7db5553 | 2018-09-15 10:35:08 +0200 | [diff] [blame] | 404 | val list = asyncStringsList() |
| 405 | val result = select<String> { |
| 406 | list.withIndex().forEach { (index, deferred) -> |
| 407 | deferred.onAwait { answer -> |
| 408 | "Deferred $index produced answer '$answer'" |
| 409 | } |
| 410 | } |
| 411 | } |
| 412 | println(result) |
| 413 | val countActive = list.count { it.isActive } |
| 414 | println("$countActive coroutines are still active") |
Prendota | 0eee3c3 | 2018-10-22 12:52:56 +0300 | [diff] [blame] | 415 | //sampleEnd |
hadihariri | 7db5553 | 2018-09-15 10:35:08 +0200 | [diff] [blame] | 416 | } |
| 417 | ``` |
| 418 | |
Alexander Prendota | cbeef10 | 2018-09-27 18:42:04 +0300 | [diff] [blame] | 419 | </div> |
| 420 | |
hadihariri | 7db5553 | 2018-09-15 10:35:08 +0200 | [diff] [blame] | 421 | > You can get full code [here](../core/kotlinx-coroutines-core/test/guide/example-select-04.kt) |
| 422 | |
| 423 | The output is: |
| 424 | |
| 425 | ```text |
| 426 | Deferred 4 produced answer 'Waited for 128 ms' |
| 427 | 11 coroutines are still active |
| 428 | ``` |
| 429 | |
| 430 | <!--- TEST --> |
| 431 | |
| 432 | ### Switch over a channel of deferred values |
| 433 | |
| 434 | Let us write a channel producer function that consumes a channel of deferred string values, waits for each received |
| 435 | deferred value, but only until the next deferred value comes over or the channel is closed. This example puts together |
| 436 | [onReceiveOrNull][ReceiveChannel.onReceiveOrNull] and [onAwait][Deferred.onAwait] clauses in the same `select`: |
| 437 | |
Alexander Prendota | cbeef10 | 2018-09-27 18:42:04 +0300 | [diff] [blame] | 438 | <div class="sample" markdown="1" theme="idea" data-highlight-only> |
| 439 | |
hadihariri | 7db5553 | 2018-09-15 10:35:08 +0200 | [diff] [blame] | 440 | ```kotlin |
| 441 | fun CoroutineScope.switchMapDeferreds(input: ReceiveChannel<Deferred<String>>) = produce<String> { |
| 442 | var current = input.receive() // start with first received deferred value |
| 443 | while (isActive) { // loop while not cancelled/closed |
| 444 | val next = select<Deferred<String>?> { // return next deferred value from this select or null |
| 445 | input.onReceiveOrNull { update -> |
| 446 | update // replaces next value to wait |
| 447 | } |
| 448 | current.onAwait { value -> |
| 449 | send(value) // send value that current deferred has produced |
| 450 | input.receiveOrNull() // and use the next deferred from the input channel |
| 451 | } |
| 452 | } |
| 453 | if (next == null) { |
| 454 | println("Channel was closed") |
| 455 | break // out of loop |
| 456 | } else { |
| 457 | current = next |
| 458 | } |
| 459 | } |
| 460 | } |
| 461 | ``` |
| 462 | |
Alexander Prendota | cbeef10 | 2018-09-27 18:42:04 +0300 | [diff] [blame] | 463 | </div> |
| 464 | |
hadihariri | 7db5553 | 2018-09-15 10:35:08 +0200 | [diff] [blame] | 465 | To test it, we'll use a simple async function that resolves to a specified string after a specified time: |
| 466 | |
Alexander Prendota | cbeef10 | 2018-09-27 18:42:04 +0300 | [diff] [blame] | 467 | |
| 468 | <div class="sample" markdown="1" theme="idea" data-highlight-only> |
| 469 | |
hadihariri | 7db5553 | 2018-09-15 10:35:08 +0200 | [diff] [blame] | 470 | ```kotlin |
| 471 | fun CoroutineScope.asyncString(str: String, time: Long) = async { |
| 472 | delay(time) |
| 473 | str |
| 474 | } |
| 475 | ``` |
| 476 | |
Alexander Prendota | cbeef10 | 2018-09-27 18:42:04 +0300 | [diff] [blame] | 477 | </div> |
| 478 | |
hadihariri | 7db5553 | 2018-09-15 10:35:08 +0200 | [diff] [blame] | 479 | The main function just launches a coroutine to print results of `switchMapDeferreds` and sends some test |
| 480 | data to it: |
| 481 | |
Prendota | 0eee3c3 | 2018-10-22 12:52:56 +0300 | [diff] [blame] | 482 | <!--- CLEAR --> |
| 483 | |
| 484 | <div class="sample" markdown="1" theme="idea" data-min-compiler-version="1.3"> |
Alexander Prendota | cbeef10 | 2018-09-27 18:42:04 +0300 | [diff] [blame] | 485 | |
hadihariri | 7db5553 | 2018-09-15 10:35:08 +0200 | [diff] [blame] | 486 | ```kotlin |
Prendota | 0eee3c3 | 2018-10-22 12:52:56 +0300 | [diff] [blame] | 487 | import kotlinx.coroutines.* |
| 488 | import kotlinx.coroutines.channels.* |
| 489 | import kotlinx.coroutines.selects.* |
| 490 | |
| 491 | fun CoroutineScope.switchMapDeferreds(input: ReceiveChannel<Deferred<String>>) = produce<String> { |
| 492 | var current = input.receive() // start with first received deferred value |
| 493 | while (isActive) { // loop while not cancelled/closed |
| 494 | val next = select<Deferred<String>?> { // return next deferred value from this select or null |
| 495 | input.onReceiveOrNull { update -> |
| 496 | update // replaces next value to wait |
| 497 | } |
| 498 | current.onAwait { value -> |
| 499 | send(value) // send value that current deferred has produced |
| 500 | input.receiveOrNull() // and use the next deferred from the input channel |
| 501 | } |
| 502 | } |
| 503 | if (next == null) { |
| 504 | println("Channel was closed") |
| 505 | break // out of loop |
| 506 | } else { |
| 507 | current = next |
| 508 | } |
| 509 | } |
| 510 | } |
| 511 | |
| 512 | fun CoroutineScope.asyncString(str: String, time: Long) = async { |
| 513 | delay(time) |
| 514 | str |
| 515 | } |
| 516 | |
Prendota | 65e6c8c | 2018-10-17 11:51:08 +0300 | [diff] [blame] | 517 | fun main() = runBlocking<Unit> { |
Prendota | 0eee3c3 | 2018-10-22 12:52:56 +0300 | [diff] [blame] | 518 | //sampleStart |
hadihariri | 7db5553 | 2018-09-15 10:35:08 +0200 | [diff] [blame] | 519 | val chan = Channel<Deferred<String>>() // the channel for test |
| 520 | launch { // launch printing coroutine |
| 521 | for (s in switchMapDeferreds(chan)) |
| 522 | println(s) // print each received string |
| 523 | } |
| 524 | chan.send(asyncString("BEGIN", 100)) |
| 525 | delay(200) // enough time for "BEGIN" to be produced |
| 526 | chan.send(asyncString("Slow", 500)) |
| 527 | delay(100) // not enough time to produce slow |
| 528 | chan.send(asyncString("Replace", 100)) |
| 529 | delay(500) // give it time before the last one |
| 530 | chan.send(asyncString("END", 500)) |
| 531 | delay(1000) // give it time to process |
| 532 | chan.close() // close the channel ... |
| 533 | delay(500) // and wait some time to let it finish |
Prendota | 0eee3c3 | 2018-10-22 12:52:56 +0300 | [diff] [blame] | 534 | //sampleEnd |
hadihariri | 7db5553 | 2018-09-15 10:35:08 +0200 | [diff] [blame] | 535 | } |
| 536 | ``` |
| 537 | |
Alexander Prendota | cbeef10 | 2018-09-27 18:42:04 +0300 | [diff] [blame] | 538 | </div> |
| 539 | |
hadihariri | 7db5553 | 2018-09-15 10:35:08 +0200 | [diff] [blame] | 540 | > You can get full code [here](../core/kotlinx-coroutines-core/test/guide/example-select-05.kt) |
| 541 | |
| 542 | The result of this code: |
| 543 | |
| 544 | ```text |
| 545 | BEGIN |
| 546 | Replace |
| 547 | END |
| 548 | Channel was closed |
| 549 | ``` |
| 550 | |
| 551 | <!--- TEST --> |
| 552 | |
| 553 | <!--- MODULE kotlinx-coroutines-core --> |
Roman Elizarov | 0950dfa | 2018-07-13 10:33:25 +0300 | [diff] [blame] | 554 | <!--- INDEX kotlinx.coroutines --> |
| 555 | [Deferred.onAwait]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-deferred/on-await.html |
| 556 | <!--- INDEX kotlinx.coroutines.channels --> |
| 557 | [ReceiveChannel.receive]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.channels/-receive-channel/receive.html |
| 558 | [ReceiveChannel.onReceive]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.channels/-receive-channel/on-receive.html |
| 559 | [ReceiveChannel.onReceiveOrNull]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.channels/-receive-channel/on-receive-or-null.html |
| 560 | [SendChannel.send]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.channels/-send-channel/send.html |
| 561 | [SendChannel.onSend]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.channels/-send-channel/on-send.html |
| 562 | <!--- INDEX kotlinx.coroutines.selects --> |
| 563 | [select]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.selects/select.html |
hadihariri | 7db5553 | 2018-09-15 10:35:08 +0200 | [diff] [blame] | 564 | <!--- END --> |