Select is not a new concept, we’ve seen it in IO multiplexing, we’ve seen it in Java NIO. The next thing I want to show you is the Select of the Kotlin coroutine.

Reuse of multiple await

We’ve seen a lot of suspend functions before, so if I have a scenario where two apis fetch data from the network and a local cache, and use the one expected to return first:

    fun CoroutineScope.getUserFromApi(login: String) = async(Dispatchers.IO){
        gitHubServiceApi.getUserSuspend(login)
    }
    
    fun CoroutineScope.getUserFromLocal(login:String)= async(Dispatchers.IO){ File(localDir, login).takeIf { it.exists() }? .readText()? .let { gson.fromJson(it, User::class.java)}
    }
Copy the code

The Deferred await returned by whichever API is called first will be suspended. To fulfill this requirement, two coroutines will have to be launched to call await, which complicates the problem.

Let’s use select to solve this problem:

    GlobalScope.launch {
        val localDeferred = getUserFromLocal(login)
        val remoteDeferred = getUserFromApi(login)
    
        valuserResponse = select<Response<User? >> { localDeferred.onAwait { Response(it,true) }
            remoteDeferred.onAwait { Response(it, false)}}... }.join()Copy the code

As you can see, instead of calling await directly, we called onAwait and registered a callback in the select. Whichever callback comes first, the SELECT immediately returns the result of the corresponding callback. If localDeferred.onawait is returned first, the value of userResponse will be Response(it, true). >.

For the example itself, if the local cache is returned first, we also need to get the network results to show the latest results:

GlobalScope.launch { ... userResponse.value? .let { log(it) } userResponse.isLocal.takeIf { it }? .let {val userFromApi = remoteDeferred.await()
        cacheUser(login, userFromApi)
        log(userFromApi)
    }
}.join()
Copy the code

Multiplexing multiple channels

The same is true for multiple channels:

    val channels = List(10) { Channel<Int>() }

select<Int? > { channels.forEach { channel -> channel.onReceive { it }// OR
        channel.onReceiveOrNull { it }
    }
}
Copy the code

For onReceive, if a Channel is closed, select directly throws an exception; For onReceiveOrNull, the it value is null if a Channel is closed.

SelectClause

How do we know which events can be selected? All events that can be selected are of type SelectClauseN, including:

  • SelectClause0: The corresponding event has no return value, for examplejoinNo return value, corresponding toonJoinThat’s the type when you use itonJoinThe argument to is a no-parameter function:
    select<Unit> {
        job.onJoin { log("Join resumed!")}}Copy the code
  • SelectClause1: The corresponding event has a return value, precedingonAwaitonReceiveThat’s all the case.
  • SelectClause2: The corresponding event has a return value, and an additional parameter is required, for exampleChannel.onSendThere are two arguments, the first one being oneChannelThe value of the data type, representing the value to be sent, and the second is the callback on success:
    List(100) { element ->
        select<Unit> {
            channels.forEach { channel ->
                channel.onSend(element) { sentChannel -> log("sent on $sentChannel")}}}}Copy the code

    When the consumer’s consumption efficiency is low, the data is sent to whomever it can be processed,onSendThe second parameter is the parameter to which the data was successfully sentChannelObject.

So if you want to verify that a suspended function supports select, you just need to check to see if the corresponding SelectClauseN exists.

summary

In coroutines, the semantics of Select are similar to Java NIO or Unix IO multiplexing, and its existence makes it easy to implement 1 drag N, which comes first. Although Select and Channel are closer to business development than the coroutine apis of the standard library, I think they are still relatively low-level API packages that can be addressed in most cases using the Flow API.

The Flow API, on the other hand, is a completely reactive programming coroutine version of the API. We can learn it from RxJava, so I’ll see you in the next article


Welcome to Kotlin Chinese community!

Chinese official website: www.kotlincn.net/

Official Chinese blog: www.kotliner.cn/

Public id: Kotlin

Zhihu column: Kotlin

CSDN: Kotlin Chinese Community

Nuggets: Kotlin Chinese Community

Brief: Kotlin Chinese Community