Day15:Channel 的第一堂课

在前面的文章中,我们介绍了 coroutine 的基本原理,如何使用 launchasync 建立 CoroutineScope,如何选择适当的调度器,让任务跑在适合的执行绪/执行绪池中等等... 不过任务在 launchasync 建立的范围中,必须要等到任务执行完成之後才能够回传,假设我们希望能够在执行到一半的时候就把目前的值传出来,我们该怎麽做呢?

换句话说,如果在 launch、async 中需要同时执行好几项任务,但是我们并不需要等待所有任务完成之後才回传,我们希望能够在一项任务完成之後就立刻回传。

Kotlin Coroutine 提供了 Channel 可以用来解决这个问题。

Channel 的意思是「通道」,也就是说我们可以在不同的 CoroutineScope 建立一个通道,让资料可以由这个通道传至其他的 Scope。

https://irrigationleadermagazine.com/wp-content/uploads/2019/10/IL-Nov_Dec-2019-5-1160x582.jpg

WHOOSHH’S INNOVATIVE FISH PASSAGE SOLUTION

上图是某公司替鲑鱼设计的一个洄游的解决方案XD

Channel Basic

让我们建立第一个 Channel :

class Day15 {
    val scope = CoroutineScope(Job())

    suspend fun firstChannel() {
        val channel = Channel<Int>()
        scope.launch {
            repeat(10) {
                delay(200)
                channel.send(it)
            }
        }
        repeat(10) {
            println(channel.receive())
        }
        println("Finish!")
    }
}

在这个 suspend 函式中,我们假设有一项任务需要做十次,每一次都需要执行 200 豪秒,我们需要在每一次的结果产生时都先将这个值传出去给其他 CoroutineScope 使用。

First Channel

从上方范例我们发现, Channel 可以在不同的 CoroutineScope 中传送资料,如上方的 scope.launch → outer scope。

FIFO

Channel 的机制类似 Queue,资料是采取FIFO(First in first out)的方式传出。

在 Channel 中,我们使用 send() 来把资料传进 Channel 中,而使用 receive() 将资料取出。

Kotlin Channel

send() 以及 receive()

如果我们将 receive() 呼叫的次数增加一次,如下:

class Day15 {
    val scope = CoroutineScope(Job())

    suspend fun firstChannel() {
        val channel = Channel<Int>()
        scope.launch {
            repeat(10) {
                delay(200)
                channel.send(it)
            }
        }
        repeat(11) {
            println(channel.receive())
        }
        println("Finish!")
    }
}

结果会是

Imgur

coroutine 不会结束, receive() 会持续等待 channel 提供资料让它取出。为什麽 receive() 可以等待呢?我想你可能猜到了,因为 receive() 也是一个 suspend 函式。

public suspend fun receive(): E

receive()

receive() 的动作是如果 channel 里面不为空,那麽就会取出资料,否则将会暂停呼叫端的 coroutine。所以上面的范例如果呼叫 receive() 的次数比 send() 的次数还要多,那麽就会暂停 coroutine,直到 channel 又有值可以让 recieve() 取出。

send()

其实, send() 也是一个 suspend 函式,只不过预设是没有作用的。

public suspend fun send(element: E)

使用 Channel() 建立 channel 的时候,如果没有带任何参数,那麽建立出来的 channel 是属於没有 缓冲区 (Buffer) 的 Rendezvous channel。

使用 Rendezvous channel 来传输资料,因为没有 Buffer ,所以传输资料的方式会是当有 send() 以及 receive() 都被呼叫的时候,这个时候资料才会传输过去,这种方式就不需要 Buffer ,呼叫 send() 时也就不需要暂停了。

小结

Channel 是采取 FIFO 的资料传输方式,也就是先传进去的资料会先取出来。而在 channel 中,如果使用 Channel() 建立 channel 时没有带入任何参数,那麽预设的容量(Capacity)就是为 0,而这边的容量指的是缓冲区(Buffer)的容量,当没有缓冲区时,资料要经由 channel 传输,则必须有人呼叫 send() 以及 receive(),这两个的呼叫的次数必须要一样,如果 receive() 呼叫的次数比 send() 还要多,表示 receive() 从 channel 取出资料的时候有可能会取不到资料,这个时候 receive() 就会暂停目前的 coroutine ,直到有另外的资料传入这个 channel()。

特别感谢

Kotlin Taiwan User Group

Kotlin 读书会


<<:  Material UI in React [ Day 19 ] Surface

>>:  Chapter3 - 动感DJ续篇 进一步操作阵列,让音乐嗨起来

我们的基因体时代-AI, Data和生物资讯 Day15- 组装後的序列档案格式SAM, BAM

上一篇我们的基因体时代-AI, Data和生物资讯 Day14- 第二代定序(次世代定序)和它的资料...

Day27 go-elasticsearch(一)

今日我们将要介绍ES官方提供go-elasticsearch客户端的基本操作。 go-elastic...

【RPA介绍】如何用UiPath Studio把重复性流程自动跑起来!

一、RPA是什麽? RPA 是 Robotic Process Automation的缩写,简称机器...

损失函数的演进--1

人脸辨识技术在Deepface出现後,就开始使用Deep learning来进行人脸辨识,在使用各种...

Day_30:让 Vite 来开启你的Vue之 内牛满面 的 完赛感言

Hi Dai Gei Ho~ 我是Winnie ~ 今天是文章的第30天,是我思思念念的这一天XD ...