Kotlin 协程 select(选择器)的源码分析

前言

多路复用(Multiplexing) 是一种技术,允许在同一通信介质或信道上传输多个信号或数据流。其目的是通过有效地共享有限的资源(如带宽、时间、物理设备等),提高通信效率,减少资源浪费。kotlin中的select和我们常见的还不太一样,这里的是一次性的操作。

Kotlin 中的 Select

协程的 select 是一种用于异步操作的选择器,它允许同时等待多个挂起函数的结果,并在其中一个完成时执行相应的操作。

select

从注释中,可以看到,支持Job, Deferred, Channel。

kotlin 复制代码
public suspend inline fun <R> select(crossinline builder: SelectBuilder<R>.() -> Unit): R {
    contract {
        callsInPlace(builder, InvocationKind.EXACTLY_ONCE)
    }
    return suspendCoroutineUninterceptedOrReturn { uCont ->
        val scope = SelectBuilderImpl(uCont)
        try {
            builder(scope)
        } catch (e: Throwable) {
            scope.handleBuilderException(e)
        }
        scope.getResult()
    }
}

invoke

传入的参数,是一个SelectBuidler接口的扩展函数。如下:

kotlin 复制代码
// SelectClause0, SelectClause1, SelectClause2的区别是传入的参数不同
public interface SelectClause0 {
    /**
     * Registers this clause with the specified [select] instance and [block] of code.
     * @suppress **This is unstable API and it is subject to change.**
     */
    @InternalCoroutinesApi
    public fun <R> registerSelectClause0(select: SelectInstance<R>, block: suspend () -> R)
}

public interface SelectBuilder<in R> {
    /**
     * Registers a clause in this [select] expression without additional parameters that does not select any value.
     */
    public operator fun SelectClause0.invoke(block: suspend () -> R)

    /**
     * Registers clause in this [select] expression without additional parameters that selects value of type [Q].
     */
    public operator fun <Q> SelectClause1<Q>.invoke(block: suspend (Q) -> R)

    public operator fun <P, Q> SelectClause2<P, Q>.invoke(param: P, block: suspend (Q) -> R)

    public operator fun <P, Q> SelectClause2<P?, Q>.invoke(block: suspend (Q) -> R): Unit = invoke(null, block)

    public fun onTimeout(timeMillis: Long, block: suspend () -> R)
}

这个地方使用了 operator 重载运算符,重载了 invoke 方法,所以我们可以像调用函数一下,直接调用这个对象,即 SelectClause0({})SelectClause0{}

所以如果我们也想让我们自定义的类有这样的功能,也可以重载这个 invoke 方法。(猜测:kotlin中默认能直接用invoke方法的那些对象,应该都是重载了的。)

由上面的代码我们就可以知道,实际上我们调用的是实现了SelectBuilder接口的SelectBuilderImpl的拓展函数。下面看看 invoke 的具体实现:

kotlin 复制代码
override fun SelectClause0.invoke(block: suspend () -> R) {
    registerSelectClause0(this@SelectBuilderImpl, block)
}

override fun <Q> SelectClause1<Q>.invoke(block: suspend (Q) -> R) {
    registerSelectClause1(this@SelectBuilderImpl, block)
}

override fun <P, Q> SelectClause2<P, Q>.invoke(param: P, block: suspend (Q) -> R) {
    registerSelectClause2(this@SelectBuilderImpl, param, block)
}

实际上调用的是对应 Job, Deferred, Channel 中具体实现的代码。

registerSelectClauseX

kotlin 复制代码
//==================================================================================

    public final override val onJoin: SelectClause0
            get() = this
    // registerSelectJoin
    public final override fun <R> registerSelectClause0(select: SelectInstance<R>, block: suspend () -> R) {
        // fast-path -- check state and select/return if needed
        loopOnState { state ->
            if (select.isSelected) return
            if (state !is Incomplete) {
                // already complete -- select result
                if (select.trySelect()) {
                    block.startCoroutineUnintercepted(select.completion)
                }
                return
            }
            if (startInternal(state) == 0) {
                // slow-path -- register waiter for completion
                select.disposeOnSelect(invokeOnCompletion(handler = SelectJoinOnCompletion(select, block).asHandler))
                return
            }
        }
    }
//==================================================================================
 
 // registerSelectAwaitInternal
    @Suppress("UNCHECKED_CAST")
    internal fun <T, R> registerSelectClause1Internal(select: SelectInstance<R>, block: suspend (T) -> R) {
        // fast-path -- check state and select/return if needed
        loopOnState { state ->
            if (select.isSelected) return
            if (state !is Incomplete) {
                // already complete -- select result
                if (select.trySelect()) {
                    if (state is CompletedExceptionally) {
                        select.resumeSelectWithException(state.cause)
                    }
                    else {
                        block.startCoroutineUnintercepted(state.unboxState() as T, select.completion)
                    }
                }
                return
            }
            if (startInternal(state) == 0) {
                // slow-path -- register waiter for completion
                select.disposeOnSelect(invokeOnCompletion(handler = SelectAwaitOnCompletion(select, block).asHandler))
                return
            }
        }
    }
    
//==================================================================================
    
    private fun <R> registerSelectSend(select: SelectInstance<R>, element: E, block: suspend (SendChannel<E>) -> R) {
        while (true) {
            if (select.isSelected) return
            if (isFullImpl) {
                val node = SendSelect(element, this, select, block)
                val enqueueResult = enqueueSend(node)
                when {
                    enqueueResult == null -> { // enqueued successfully
                        select.disposeOnSelect(node)
                        return
                    }
                    enqueueResult is Closed<*> -> throw recoverStackTrace(helpCloseAndGetSendException(element, enqueueResult))
                    enqueueResult === ENQUEUE_FAILED -> {} // try to offer
                    enqueueResult is Receive<*> -> {} // try to offer
                    else -> error("enqueueSend returned $enqueueResult ")
                }
            }
            // hm... receiver is waiting or buffer is not full. try to offer
            val offerResult = offerSelectInternal(element, select)
            when {
                offerResult === ALREADY_SELECTED -> return
                offerResult === OFFER_FAILED -> {} // retry
                offerResult === RETRY_ATOMIC -> {} // retry
                offerResult === OFFER_SUCCESS -> {
                    block.startCoroutineUnintercepted(receiver = this, completion = select.completion)
                    return
                }
                offerResult is Closed<*> -> throw recoverStackTrace(helpCloseAndGetSendException(element, offerResult))
                else -> error("offerSelectInternal returned $offerResult")
            }
        }
    }

    private inline fun loopOnState(block: (Any?) -> Unit): Nothing {
        while (true) {
            block(state)
        }
    }

从上述代码,可以看到,大致的逻辑还是差不多的。在循环中去判断状态,直到协程执行结束后调用select中的resume方法,返回结果给select

总结

kotlin 中的 select 中主要是使用到了续体风格传递,将结果通过Result传递给外层的续体。当多个协程要唤醒同一续体时,会通过 CAS 去判断续体的状态。所以就能达到使用少资源去监听多任务的目的,当有返回值时,就直接返回做早的结果。

主要知识:

  • invoke的重载
  • 续体传递、状态机(本文解析未涉及到)
  • 协程调度(本文解析未涉及到)
相关推荐
alexhilton2 小时前
Compose多平台 (CMP) 开发的四个实用技巧
android·kotlin·android jetpack
xvch6 小时前
Kotlin 2.1.0 入门教程(一)
android·kotlin
我命由我123457 小时前
11-1.Android 项目结构 - androidTest 包与 test 包(单元测试与仪器化测试)
android·开发语言·ide·java-ee·单元测试·kotlin·android studio
划水哥~11 小时前
Kotlin构造函数
开发语言·kotlin
小林爱13 小时前
【Compose multiplatform教程】05 IOS环境编译
android·前端·ios·kotlin·android studio·多平台
zhangphil14 小时前
Android BitmapShader更简易的实现刮刮乐功能,Kotlin
android·kotlin
梦0719 小时前
学习笔记-Kotlin
笔记·学习·kotlin
Yang-Never1 天前
Shader -> BitmapShader贴图着色器详解
android·开发语言·kotlin·android studio·贴图·着色器
命运之手1 天前
[ Kotlin ] Integrate ProtoBuffer and GoogleRPC Into KotlinNative
android·kotlin·grpc·proto-buffer·kotlin-native