前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >【Kotlin 协程】协程中的多路复用技术 ② ( select 函数原型 | SelectClauseN 事件 | 查看挂起函数是否支持 select )

【Kotlin 协程】协程中的多路复用技术 ② ( select 函数原型 | SelectClauseN 事件 | 查看挂起函数是否支持 select )

作者头像
韩曙亮
发布2023-03-30 18:33:40
1.2K0
发布2023-03-30 18:33:40
举报
文章被收录于专栏:韩曙亮的移动开发专栏

文章目录

一、select 函数原型


在上一篇博客 【Kotlin 协程】协程中的多路复用技术 ① ( 多路复用技术 | await 协程多路复用 | Channel 通道多路复用 ) 中 , 介绍了 协程多路复用技术 , 多路复用 主要使用 select 代码块 实现 ,

  • 在 select 代码块中 调用多个协程的 onAwait 函数 , 哪个协程先返回数据 , 就选择该协程的数据作为返回值 ;
代码语言:javascript
复制
// 同时执行两个协程, 哪个先执行完毕, 就取哪个协程的执行结果
val data = select<String> {
	localJob.onAwait{it}
	remoteJob.onAwait{it}
}
  • 在 select 代码块中 调用多个 Channel 通道的 onReceive 函数 , 哪个通道先返回数据 , 就选择该通道的数据作为返回值 ;
代码语言:javascript
复制
val num = select<Int> {
	channel0.onReceive {it}
	channel1.onReceive {it}
}

上述多路复用都用到了 select 函数 , 其函数原型如下 :

代码语言:javascript
复制
/**
 * 同时等待使用_clauses_指定的多个挂起函数的结果
 * 在此选择调用的[builder]范围内。调用者被挂起,直到其中一个子句
 * 是_selected_或_fails_。
 *
 * 最多一个子句被*原子地*选中,并且它的块被执行。所选子句的结果
 * 成为选择的结果。如果有任何子句_fails_,则选择调用将生成
 * 相应的异常。在本例中没有选择子句。
 *
 * 这个选择函数是_biased_到第一个子句。当可以同时选择多个子句时,
 * 第一个有优先权。使用[selectUnbiased]表示无偏(随机)选择
 * 条款。

 * 选择表达式没有' default '子句。相反,每个可选择的挂起函数都具有
 * 对应的非挂起版本,可以与常规的“when”表达式一起使用来选择一个
 * 的选项,如果没有选项可以立即选择,则执行默认(' else ')操作。
 *
 * ###支持的选择方法列表
 *
 * | **Receiver**     | **Suspending function**                           | **Select clause**
 * | ---------------- | ---------------------------------------------     | -----------------------------------------------------
 * | [Job]            | [join][Job.join]                                  | [onJoin][Job.onJoin]
 * | [Deferred]       | [await][Deferred.await]                           | [onAwait][Deferred.onAwait]
 * | [SendChannel]    | [send][SendChannel.send]                          | [onSend][SendChannel.onSend]
 * | [ReceiveChannel] | [receive][ReceiveChannel.receive]                 | [onReceive][ReceiveChannel.onReceive]
 * | [ReceiveChannel] | [receiveCatching][ReceiveChannel.receiveCatching] | [onReceiveCatching][ReceiveChannel.onReceiveCatching]
 * | [Mutex]          | [lock][Mutex.lock]                                | [onLock][Mutex.onLock]
 * | none             | [delay]                                           | [onTimeout][SelectBuilder.onTimeout]
 *
 * 这个暂停函数是可以取消的。如果当前协程的[Job]被取消或完成
 * 函数挂起后,该函数立即恢复[CancellationException]。
 * 有**立即取消保证**。如果作业被取消,而此函数被取消
 * 暂停,将无法成功恢复。参见[suspendCancellableCoroutine]文档了解底层细节。
 *
 * 注意,该函数在未挂起时不会检查是否取消。
 * 使用[yield]或[CoroutineScope。isActive]如果需要,在紧循环中定期检查取消。
 */
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()
    }
}

二、Select clause 事件


协程中的多路复用 主要是在 select 代码块中实现 ,

能够在 select 中执行的多路复用事件 , 称为 SelectClauseN 事件 :

  • SelectClause0 事件 : 没有返回值 , 没有参数 ; 如 : onJoin 事件 ;
  • SelectClause1 事件 : 有返回值 , 没有参数 ; 如 : onAwait 事件 和 onReceive 事件 ;
  • SelectClause2 事件 : 有返回值 , 有参数 ; 如 : Channel 通道的 onSend 事件 ;

挂起函数 如果存在对应的 SelectClauseN 事件 , 那么就可以使用 select 实现多路复用 ;

1、SelectClause0 事件代码示例

代码示例 :

代码语言:javascript
复制
package kim.hsl.coroutine

import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.selects.select

class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        runBlocking {
            val job0 = GlobalScope.launch {
                delay(500)
                println("job0")
            }

            val job1 = GlobalScope.launch {
                delay(1000)
                println("job1")
            }

            // 由于没有返回值 , select 后泛型为 Unit
            select<Unit> {
                job0.onJoin { println("job0.onJoin") }
                job1.onJoin { println("job1.onJoin") }
            }

            // 等待所有协程执行完毕
            delay(1500)
        }
    }
}

执行结果 :

代码语言:javascript
复制
23:17:27.355 System.out   kim.hsl.coroutine     I  job0
23:17:27.357 System.out   kim.hsl.coroutine     I  job0.onJoin
23:17:27.861 System.out   kim.hsl.coroutine     I  job1

2、SelectClause2 事件代码示例

代码示例 :

代码语言:javascript
复制
package kim.hsl.coroutine

import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import kotlinx.coroutines.*
import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.selects.select

class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        runBlocking {
            val channel0 = Channel<Int>()
            val channel1 = Channel<Int>()

            launch (Dispatchers.IO) {
                select<Unit> {
                    launch {
                        delay(500)
                        channel0.onSend(500) {
                            println("channel0 通道发送 500")
                        }
                    }

                    launch {
                        delay(1000)
                        channel1.onSend(1000) {
                            println("channel1 通道发送 1000")
                        }
                    }
                }
            }

            GlobalScope.launch {
                println("channel0 通道接收数据 : ${channel0.receive()}")
            }

            GlobalScope.launch {
                println("channel1 通道接收数据 : ${channel1.receive()}")
            }

            // 确保上述协程执行完毕
            delay(1500)
        }
    }
}

执行结果 :

代码语言:javascript
复制
23:26:10.202 System.out   kim.hsl.coroutine     I  channel0 通道接收数据 : 500
23:26:10.207 System.out   kim.hsl.coroutine     I  channel0 通道发送 500

三、查看挂起函数是否支持 select


如果查看某个挂起函数是否支持 select , 直接进入该函数源码 , 查看其是否定义了对应的 SelectClauseN 类型 , 如查看 Channel#onSend 函数原型时 , 其声明了 onSend 类型为 SelectClause2<E, SendChannel<E>> , 说明这是一个 SelectClause2 事件 , 可用于 select 多路复用 ;

代码语言:javascript
复制
/**
 * 子句用于[send]暂停函数的[select]表达式,该表达式在指定元素时进行选择
 * 当参数被发送到通道时。子句被选中时,对该通道的引用
 * 传递到相应的块中。
 *
 * 如果通道[为' send '关闭][isClosedForSend](参见[close]了解详细信息),则[select]调用失败并出现异常。
 */
public val onSend: SelectClause2<E, SendChannel<E>>

另外也可以参考下面的表格中的 Select clause 定义 , 这是 select 函数文档内容 :

Receiver

Suspending function

Select clause

Job

join

onJoin

Deferred

await

onAwait

SendChannel

send

onSend

ReceiveChannel

receive

onReceive

ReceiveChannel

receiveCatching

onReceiveCatching

Mutex

lock

onLock

none

delay

onTimeout

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2022-12-31,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 文章目录
  • 一、select 函数原型
  • 二、Select clause 事件
    • 1、SelectClause0 事件代码示例
      • 2、SelectClause2 事件代码示例
      • 三、查看挂起函数是否支持 select
      领券
      问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档