or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

channels.mdcoroutine-builders.mdcoroutine-management.mddispatchers.mderror-handling.mdflow-api.mdindex.mdselect-expression.mdsynchronization.md

select-expression.mddocs/

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

```