0
# Select Expression
1
2
Multi-way suspending choice allowing selection among multiple suspend operations. Select expressions provide a way to wait for multiple suspend operations concurrently and proceed with the first one that completes.
3
4
## Capabilities
5
6
### Select Function
7
8
Core function for creating select expressions.
9
10
```kotlin { .api }
11
/**
12
* Waits for the result of multiple suspend operations simultaneously
13
* @param builder block that configures the select clauses
14
* @return result from the first clause that completes
15
*/
16
suspend fun <R> select(builder: SelectBuilder<R>.() -> Unit): R
17
18
/**
19
* Unbiased version of select (fair selection)
20
*/
21
suspend fun <R> selectUnbiased(builder: SelectBuilder<R>.() -> Unit): R
22
```
23
24
### Select Builder
25
26
DSL builder for configuring select clauses.
27
28
```kotlin { .api }
29
/**
30
* Builder for select expressions
31
*/
32
interface SelectBuilder<in R> {
33
/** Configure a select clause with no parameters */
34
fun SelectClause0.invoke(block: suspend () -> R)
35
36
/** Configure a select clause with one parameter */
37
fun <P> SelectClause1<P>.invoke(block: suspend (P) -> R)
38
39
/** Configure a select clause with two parameters */
40
fun <P, Q> SelectClause2<P, Q>.invoke(param: P, block: suspend (Q) -> R)
41
42
/** Add timeout clause */
43
fun onTimeout(timeMillis: Long, block: suspend () -> R)
44
}
45
```
46
47
### Select Clauses
48
49
Interfaces for different types of select clauses.
50
51
```kotlin { .api }
52
/**
53
* Select clause with no parameters
54
*/
55
interface SelectClause0
56
57
/**
58
* Select clause with one parameter
59
*/
60
interface SelectClause1<out T>
61
62
/**
63
* Select clause with two parameters
64
*/
65
interface SelectClause2<in P, out T>
66
```
67
68
## Usage with Channels
69
70
Channels provide select clauses for non-blocking operations.
71
72
**Usage Examples:**
73
74
```kotlin
75
import kotlinx.coroutines.*
76
import kotlinx.coroutines.channels.*
77
import kotlinx.coroutines.selects.*
78
79
suspend fun channelSelect() {
80
val channel1 = Channel<String>()
81
val channel2 = Channel<Int>()
82
83
// Launch producers
84
launch {
85
delay(100)
86
channel1.send("Hello")
87
}
88
89
launch {
90
delay(200)
91
channel2.send(42)
92
}
93
94
// Select between channels
95
val result = select<String> {
96
channel1.onReceive { value ->
97
"String: $value"
98
}
99
channel2.onReceive { value ->
100
"Number: $value"
101
}
102
onTimeout(500) {
103
"Timeout occurred"
104
}
105
}
106
107
println(result) // "String: Hello"
108
}
109
110
// Fan-in pattern with select
111
suspend fun fanIn(channels: List<ReceiveChannel<String>>): ReceiveChannel<String> {
112
return produce {
113
while (true) {
114
val value = select<String?> {
115
channels.forEach { channel ->
116
channel.onReceiveCatching { result ->
117
result.getOrNull()
118
}
119
}
120
}
121
if (value != null) {
122
send(value)
123
} else {
124
break // All channels closed
125
}
126
}
127
}
128
}
129
```
130
131
## Usage with Jobs
132
133
Jobs provide select clauses for waiting on completion.
134
135
```kotlin
136
import kotlinx.coroutines.*
137
import kotlinx.coroutines.selects.*
138
139
suspend fun jobSelect() {
140
val job1 = async {
141
delay(100)
142
"Fast result"
143
}
144
145
val job2 = async {
146
delay(200)
147
"Slow result"
148
}
149
150
// Wait for first job to complete
151
val result = select<String> {
152
job1.onAwait { "Job1: $it" }
153
job2.onAwait { "Job2: $it" }
154
}
155
156
println(result) // "Job1: Fast result"
157
158
// Cancel remaining job
159
job2.cancel()
160
}
161
162
// Racing multiple async operations
163
suspend fun raceOperations() {
164
val operations = listOf(
165
async { fetchFromServer1() },
166
async { fetchFromServer2() },
167
async { fetchFromCache() }
168
)
169
170
val winner = select<String> {
171
operations.forEachIndexed { index, deferred ->
172
deferred.onAwait { result ->
173
"Server $index: $result"
174
}
175
}
176
onTimeout(1000) {
177
"All operations timed out"
178
}
179
}
180
181
// Cancel remaining operations
182
operations.forEach { it.cancel() }
183
184
return winner
185
}
186
```
187
188
## Advanced Select Patterns
189
190
### Load Balancing
191
192
Distribute work across multiple workers using select.
193
194
```kotlin
195
class LoadBalancer {
196
private val workers = List(3) { Channel<Work>() }
197
198
suspend fun submitWork(work: Work) {
199
// Send to first available worker
200
select<Unit> {
201
workers.forEach { worker ->
202
worker.onSend(work) {
203
println("Work sent to worker")
204
}
205
}
206
}
207
}
208
209
fun startWorkers() {
210
workers.forEachIndexed { index, channel ->
211
launch {
212
for (work in channel) {
213
processWork(work, "Worker-$index")
214
}
215
}
216
}
217
}
218
}
219
```
220
221
### Timeout with Fallback
222
223
Implement timeout with graceful fallback.
224
225
```kotlin
226
suspend fun fetchWithFallback(): String {
227
return select {
228
// Primary operation
229
async { fetchFromPrimarySource() }.onAwait {
230
"Primary: $it"
231
}
232
233
// Timeout with fallback
234
onTimeout(1000) {
235
// Try fallback source
236
select<String> {
237
async { fetchFromFallbackSource() }.onAwait {
238
"Fallback: $it"
239
}
240
onTimeout(500) {
241
"Default value"
242
}
243
}
244
}
245
}
246
}
247
```
248
249
### Producer-Consumer with Select
250
251
Coordinate multiple producers and consumers.
252
253
```kotlin
254
class ProducerConsumerSystem {
255
private val highPriority = Channel<Task>(capacity = 10)
256
private val lowPriority = Channel<Task>(capacity = 100)
257
258
suspend fun processTask(): Task? {
259
return select {
260
// Prefer high priority tasks
261
highPriority.onReceiveCatching { result ->
262
result.getOrNull()
263
}
264
265
// Fall back to low priority if high priority is empty
266
lowPriority.onReceiveCatching { result ->
267
if (highPriority.isEmpty) {
268
result.getOrNull()
269
} else {
270
null // Skip low priority if high priority has tasks
271
}
272
}
273
}
274
}
275
}
276
```
277
278
## Select vs Other Patterns
279
280
### Select vs async/await
281
282
```kotlin
283
// Select - first wins, others cancelled
284
val result = select {
285
async { operation1() }.onAwait { "Op1: $it" }
286
async { operation2() }.onAwait { "Op2: $it" }
287
}
288
289
// async/await - wait for all
290
val results = listOf(
291
async { operation1() },
292
async { operation2() }
293
).awaitAll()
294
```
295
296
### Select vs Channel.receive
297
298
```kotlin
299
// Blocking receive - waits for specific channel
300
val value = channel.receive()
301
302
// Select receive - first available channel
303
val value = select {
304
channel1.onReceive { "From 1: $it" }
305
channel2.onReceive { "From 2: $it" }
306
}
307
```
308
309
## Best Practices
310
311
### Resource Cleanup
312
313
Always clean up resources in select expressions:
314
315
```kotlin
316
suspend fun selectWithCleanup() {
317
val resource1 = acquireResource()
318
val resource2 = acquireResource()
319
320
try {
321
val result = select {
322
resource1.onComplete { "Result 1: $it" }
323
resource2.onComplete { "Result 2: $it" }
324
}
325
return result
326
} finally {
327
resource1.cleanup()
328
resource2.cleanup()
329
}
330
}
331
```
332
333
### Avoid Select in Hot Loops
334
335
Select has overhead, avoid in tight loops:
336
337
```kotlin
338
// Inefficient
339
while (true) {
340
select {
341
channel.onReceive { process(it) }
342
onTimeout(10) { /* check condition */ }
343
}
344
}
345
346
// Better
347
for (value in channel) {
348
process(value)
349
if (shouldStop()) break
350
}
351
```
352
353
### Fair Selection
354
355
Use `selectUnbiased` for fair selection when order matters:
356
357
```kotlin
358
// Biased - earlier clauses preferred
359
select {
360
channel1.onReceive { /* ... */ }
361
channel2.onReceive { /* ... */ }
362
}
363
364
// Unbiased - fair selection
365
selectUnbiased {
366
channel1.onReceive { /* ... */ }
367
channel2.onReceive { /* ... */ }
368
}
369
```