掌握Kotlin Coroutine之 选择表达式
source link: http://blog.chengyunfeng.com/?p=1093
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
理解了前面所介绍的内容,在应用开发中基本就可以使用Coroutine了。从这一节开始会涉及到几个比较复杂并且不常用的概念。
首先来看看Select expression(选择表达式)
。选择表达式可以同时等待多个suspending函数
并选择
第一个执行完的结果。 通俗点说呢,就是有多个异步任务在同时执行,然后等待这些任务的执行,如果谁最先执行完并返回结果,则就使用这个最先完成的任务返回的数据,丢弃其他后执行完的数据。Coroutine 对这种场景提供了不同的支持。
注意选择表达式目前还处于实验性阶段,以后其API可能会发生变化。
从通道中选择
这种场景是有多个生成数据的通道(channel
),然后选择最先收到数据的那个通道。下面是一个示例,有两个函数fizz
和buzz
分别返回一个每隔一段时间产生一个字符串的通道channel
对象:
下面是使用select
表达式来选择最先产生数据的那个通道产生的数据,selectFizzBuzz
函数返回select
表达式返回的值:
select
表达式还支持超时操作,如果多个通道在规定的时间内都没法产生数据,则可以通过调用onTimeout
函数来处理。
下面通过示例代码看起来更加直观:
上面调用selectFizzBuzz
这个函数7次,第一次执行的时候, select
表达式等待了 500毫秒还没有收到两个通道生成的数据,所以就触发了超时,所以第一个打印的结果为 In repeat index 0 -> value Default
; 后面第二次执行的时候,继续等待收到了 fizz 生成的数据 Fizz
;以此类推。
上面的示例代码运行结果如下:
通道关闭异常
如果通道被关闭了,则select
里面的onReceive
语句会抛出如下异常:
kotlinx.coroutines.channels.ClosedReceiveChannelException: Channel was closed
使用onReceiveOrNull
函数可以在通道关闭的时候收到 null 值,而不是抛出异常。这样应用可以针对 null 值做一些特殊处理。
上面的示例代码和前面相比做了一些改动,首先发送数据的通道不再设置延迟时间,取消了timeout
语句,并且发送数据的次数做了限制,还处理了当通道关闭后返回值为null
的情况。
上面代码的log如下:
上面这个示例演示了几个select
的特性:
– 优先选择第一个,当几个选择语句同时返回数据的时候,select
优先选第一个语句所返回的数据,在上面的示例中,两个通道不停的产生数据,而fizz
出现在select
语句中第一个位置,所以优先选择fizz
的结果
– 由于两个通道用的都是不带缓冲的类型,所以在调用fizz
的send
函数的时候会被暂停执行,这样buzz
也会有执行的机会。
– 另外当通道被关闭后会立刻返回null,所以最后两个是fizz
为null的情况
SendChannel#onSend
SendChannel
接口有个 onSend
变量定义,这个onSend
可以用到选择表达式中,其作用就是当向通道发送数据的时候,就触发选择表达式选择这个onSend
语句。 这个解释起来有点绕,下面通过代码来演示:
上面的代码定义了一个produceNumbers
扩展函数,该函数返回值类型为ReceiveChannel<Int>
(假设这个为消费数据的主通道),其功能就是返回从1到6这六个数字。然后下面通过produceNumbers(side).consumeEach()
函数来接收这六个数字。正常情况下,我们需要在 produce()
代码块里面调用SendChannel
的send()
函数来向这个通道发送数据。 但是上面的produceNumbers
扩展函数使用选择表达式来发送数据。里面的选择表达式的意思是:分别向主通道和次要通道(side)发送数据,发送的顺序是选择表达式中定义的通道顺序,如果第一个主通道上一个数据还没处理完成,则就向下一个通道(side)继续发送,当数据发送出去后,调用onSend
语句,由于上面示例中,主通道每隔 250毫秒处理一个数据,所以只写的Log如下:
如果上面的解释还看的不是很明白,则可以再详细看看下面改进够的代码,更容易理解:
注意:上面的代码依然需要仔细的看才能理解 onSend 在选择表达式中的用法。
上面改进后的代码打印的log如下:
看log的时候请注意,pro 通道在处理数据的时候延迟了 250毫秒,而1到6六个数字每次延迟100毫秒。
选择异步数据(deferred value)
Deferred
接口有个onAwait
属性也可以用在选择表达式中,用来选择async
异步操作的返回值。比如下面这个的示例:
上面执行的log如下:
注意上面的选择表达式的实现,由于选择表达式是一个 Kotlin DSL,所以可以用任意的方式来提供这个选择语句。上面使用便利的操作来提供
onAwait
语句。和下面的 for 循环类似。
处理产生deferred类型数据的通道
下面这个示例比较有意思,switchMapDeferreds
函数的参数为ReceiveChannel<Deferred<String>>
类型,所以 input 里面接收到的数据类型为Deferred<String>
,然后当收到 input 通道发来的 deferred 数据后,开始等待该 deferred 执行完毕并同时等待 input 通道的下一个数据,如果 deferred 还没有执行完毕则 input 下一个数据(next)就已经收到了,那么就不继续等待当前这个 deferred 对象的返回值了,而是继续等待 next 上的返回值,依次循环。
请注意看上面的注释可以帮助理解该代码的运行行为。这里同时在选择表达式上使用了
ReceiveChannel#onReceiveOrNull
和Deferred#onAwait
两个不同的 select 语句。
可以使用下面的函数来生成向 input 通道内发送的 deferred 对象:
下面是整个测试代码:
注意看上面代码中的注释,帮助理解 调用switchMapDeferreds
函数的代码逻辑。特别是向 chan 通道发送数据的等待时间间隔。下面是log:
可以看到选择表达式Select expression
可以有不同的用法,可以在多个suspending functions
上等待并返回最先执行完毕的数据。 需要注意的是,选择表达式还处于实现性阶段,以后这些API可能会有稍微的变化。请及时关注官方文档。
Recommend
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK