kotlinx.coroutines Channel

Channel

Channel 的概念与BlockingQueue相似两者最大的不同在于,后者阻塞式的put操作变成了挂起等待的send,而阻塞式的take变成了挂起等待的receive

    val channel = Channel<Int>()
    launch {
        // 这里可能是消耗大量 CPU 运算的异步逻辑,我们将仅仅做 5 次整数的平方并发送
        for (x in 1..5) channel.send(x * x)
    }
// 这里我们打印了 5 次被接收的整数:
    repeat(5) { println(channel.receive()) }
    println("Done!")
}

/*
1
4
9
16
25
Done!
*/

与队列不同channel可以关闭,这表明没有更多的元素了,在接收者中可以定期的使用for循环从channel 中接收元素

    val channel = Channel<Int>()
    launch {
        // 这里可能是消耗大量 CPU 运算的异步逻辑,我们将仅仅做 5 次整数的平方并发送
        for (x in 1..5) channel.send(x * x)
        channel.close()//这里结束发送
    }
// 这里我们使用 `for` 循环来打印所有被接收到的元素(直到通道被关闭)
    for (y in channel) println(y)
    println("Done!")
}

/*
1
4
9
16
25
Done!
*/

构建Channel 生产者

协程持续生成一列数据的模型很常用,就是生产者——消费者模式的一部分,这在并发代码中很常见。你可以选择把这样一个生产者抽象成一个函数,你可以将生产者抽象成一个函数,并且使channel作为它的参数,但这与必须从函数中返回结果的常识相违悖.
这里提供了一个 produce 函数,它可以很方便地构造协程,并且使得生产者的操作更为简便,还有一个扩展函数 consumeEach,它替代了消费者的 for 循环

fun CoroutineScope.produceNumbers() = produce<Int> {
    for (x in 1..5) send(x * x)
}

fun main(args: Array<String>)= runBlocking<Unit> {
    val squares = produceNumbers()
    squares.consumeEach { println(it) }
    println("Done!")
}
/*
1
4
9
16
25
Done!
 */

使用Channel 生成素数

fun CoroutineScope.numbersFrom(start: Int) = produce<Int> {
    var x = start
    while (true) send(x++) // 开启了一个从start开始的无限的整数流
}
fun CoroutineScope.filter(numbers: ReceiveChannel<Int>, prime: Int) = produce<Int> {
    for (x in numbers) if (x % prime != 0) send(x)
}
fun main(args: Array<String>)= runBlocking<Unit> {
    var cur = numbersFrom(2)
    for (i in 1..10) {
        val prime = cur.receive()
        println(prime)
        cur = filter(cur, prime)
    }
    coroutineContext.cancelChildren() // 取消所有的子协程来让主协程结束
}

cancelChildren方法可以取消所有子协程

一个生产者,多个消费者

fun CoroutineScope.produceNumbers() = produce<Int> {
    var x = 0
    while (true){
        send(x++) // 开启了一个无限的整数流
        delay(100L)//延迟0.1S
    }
}

fun main(args: Array<String>)= runBlocking<Unit> {
    fun launchProcessor(id: Int, channel: ReceiveChannel<Int>) = launch {
        channel.consumeEach {
            println("Processor #$id received $it")
        }
    }
    val producer = produceNumbers()
    repeat(5) { launchProcessor(it, producer) }
    delay(950L)
    producer.cancel() // 取消生产者协程,kill 所有
}
/**
 * 
 Processor #0 received 0
Processor #0 received 1
Processor #1 received 2
Processor #2 received 3
Processor #3 received 4
Processor #4 received 5
Processor #0 received 6
Processor #1 received 7
Processor #2 received 8
Processor #3 received 9
 */

多个协程可以接收来自同一个channel的数据,它们之间会做分发处理

多个生产者,一个消费者

suspend fun sendString(channel: SendChannel<String>, s: String, time: Long) {
    while (true) {
        delay(time)
        channel.send(s)
    }
}
fun main(args: Array<String>)= runBlocking<Unit> {
    val channel = Channel<String>()
    launch { sendString(channel, "foo", 200L) }
    launch { sendString(channel, "BAR!", 500L) }
    repeat(6) { // 接收前六个
        println(channel.receive())
    }
    coroutineContext.cancelChildren() // 取消所有子协程来让主协程结束
}
/**
foo
foo
BAR!
foo
foo
BAR!
 */

多个协程也可以往同一个Channel 发送数据

带缓冲的Channel

没有缓冲的 channel 会在发送者和接收者都准备好之后进行数据传输,如果 send先调用,那么它会挂起等到 receive也被调用.
Channelproduce 函数都有一个参数capacity,用于指定缓冲区的大小,。缓冲区允许发送者在挂起等待之前先发送几条数据,这和指定 capacityBlockingQueue 相似,缓冲区满了就会阻塞。

fun main(args: Array<String>)= runBlocking<Unit> {
    val channel = Channel<Int>(4) // 启动带缓冲的通道
    val sender = launch { // 启动发送者协程
        repeat(10) {
            println("Sending $it") // 在每一个元素发送前打印它们
            channel.send(it) // 将在缓冲区被占满时挂起
        }
    }
// 没有接收到东西……只是等待……
    delay(1000)
    sender.cancel() // 取消发送者协程
}
/**
Sending 0
Sending 1
Sending 2
Sending 3
Sending 4
 */

Channel 公平

发送和接收都是公平的,使是从不同的协程调用,它也严格按照调用的顺序分配。采取的原则是先进先出

data class Ball(var hits: Int)

suspend fun player(name: String, table: Channel<Ball>) {
    for (ball in table) { // 在循环中接收球
        ball.hits++
        println("$name $ball")
        delay(300) // 等待一段时间
        table.send(ball) // 将球发送回去
    }
}
fun main(args: Array<String>)= runBlocking<Unit> {
    val table = Channel<Ball>() // 一个共享的 table(桌子)
    launch { player("ping", table) }
    launch { player("pong", table) }
    table.send(Ball(0)) // 乒乓球
    delay(1000) // 延迟 1 秒钟
    coroutineContext.cancelChildren() // 游戏结束,取消它们
}
/**
ping Ball(hits=1)
pong Ball(hits=2)
ping Ball(hits=3)
pong Ball(hits=4)
 */

注意,有时因为执行的特性,channel 的生产者的执行并不公平,具体看这个 issue

计时器Channel

计时器Channel 是一种特别的会合Channel,每次经过特定的延迟都会从该通道进行消费并产生Unit ,虽然它看起来似乎没用,它被用来构建分段来创建复杂的基于时间的 produce Channel 和进行窗口化操作以及其它时间相关的处理。 可以在 select 中使用计时器Channel 来进行“打勾”操作。
使用工厂方法ticker来创建Channel, 为了表明不需要其它元素,请使用 ReceiveChannel.cancel方法
需要注意,ticker 很关注消费者的暂停,默认情况下,如果发生了停顿就会判断下一次生产的元素的延时,你需要试着维护一个固定频率的生产速度。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 160,277评论 4 364
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,777评论 1 298
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,946评论 0 245
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,271评论 0 213
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,636评论 3 288
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,767评论 1 221
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,989评论 2 315
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,733评论 0 204
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,457评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,674评论 2 249
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,155评论 1 261
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,518评论 3 258
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,160评论 3 238
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,114评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,898评论 0 198
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,822评论 2 280
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,705评论 2 273

推荐阅读更多精彩内容