Select in Kotlin Coroutines
This is a chapter from the book Kotlin Coroutines. You can find it on LeanPub or Amazon.
Coroutines provide the select
function, which lets us await the result of the first coroutine that completes. It also offers the possibility of sending to the first channel that has space in the buffer or receiving from the first channel that has an available element. This lets us make races between coroutines or join results from multiple sources. Let's see it in practice.
The
select
function is still experimental, even though this feature has been available since early Kotlin Coroutines releases. It is highly unlikely thatselect
will be removed, but its API might still change. This feature will probably never be stabilized because there is little demand for it - it is actually used rather rarely, so I decided to keep this chapter as short as possible.
Selecting deferred values
Let's say that we want to request data from multiple sources, but we're only interested in the fastest response. The easiest way to achieve this is to start these requests in async processes; then, use the select
function as an expression, and await different values inside it. Inside select
, we can call onAwait
on Deferred
value, which specifies a possible select expression result. Inside its lambda expression, you can transform the value. In the example below, we just return an async result, so the select
expression will complete once the first async task is completed, then it will return its result.
Notice that after the
select
expression, we callcancelChildren
oncoroutineContext
to cancel all the coroutines that were started inside theselect
expression. This is important because without it,coroutineScope
would not complete until all the coroutines started inside its scope are completed.
The solution above is a bit complex, which is why many developers define a helper function raceOf
or use an external library (like Splitties by Louis CAD) that includes such a function. The raceOf
function is a simple helper function that takes a lambda expression with multiple async tasks and returns the result of the first one that completes.
Selecting from channels
The select
function can also be used with channels. These are the main functions that can be used inside it:
onReceive
- selected when this channel has a value. It receives this value (like thereceive
method) and uses it as an argument for its lambda expression. WhenonReceive
is selected,select
returns the result of its lambda expression.onReceiveCatching
- selected when this channel has a value or is closed. It receivesChannelResult
, which either represents a value or signals that this channel is closed, and it uses this value as an argument for its lambda expression. WhenonReceiveCatching
is selected,select
returns the result of its lambda expression.onSend
- selected when this channel has space in the buffer. It sends a value to this channel (like thesend
method) and invokes its lambda expression with a reference to the channel. WhenonSend
is selected,select
returnsUnit
.
Beware, that if a channel is closed, the
onReceive
function will throw an exception, whileonReceiveCatching
will returnChannelResult.Closed
. Those operations are immediately selected when the channel is closed, so if you want to ignore elements from a closed channel, you should not select from it. This can be achieved by using a simple if-statement inside theselect
expression.
The select expression can be used with onReceive
or onReceiveCatching
to receive from multiple channels.
The select function can be used with onSend
to send to the first channel that has space in the buffer.
Summary
select
is a useful function that lets us await the result of the first coroutine that completes, or send to or receive from the first from multiple channels. It is mainly used to implement different patterns for operating on channels, but it can also be used to implement coroutine races.