Kotlin协程源码分析(二)之Channel

这章理一下channel,先分享一句学习时候看到的话:Do not communicate by sharing memory; instead, share memory by communicating.。本来好像是用在go上的,但也有着异曲同工之妙啊

channel顾名思义是管道,有入口与出口。因此最底层有sendChannel&receiveChannel

produce

Produce = Coroutine + Channel

example:

val channel: ReceiveChannel<Int> = produce<Int>(CommonPool) {
    for (i in 0 .. 100) {
        delay(1000)
        channel.send(i)
    }
}

launch(UI) {
    for (number in channel) {
        textView.text = "Latest number is $number"
    }
}

produce也是产生协程,跟普通的launch不同他会返回一个receiveChannel,后面会看到receiveChannel是一个迭代器,同时会suspendhasNext和next()上,因此另一个协程就可以使用for...in...等待接受。

@ExperimentalCoroutinesApi
public fun <E> CoroutineScope.produce(
    context: CoroutineContext = EmptyCoroutineContext,
    capacity: Int = 0,
    @BuilderInference block: suspend ProducerScope<E>.() -> Unit
): ReceiveChannel<E> {
    val channel = Channel<E>(capacity)
    val newContext = newCoroutineContext(context)
    val coroutine = ProducerCoroutine(newContext, channel)
    coroutine.start(CoroutineStart.DEFAULT, coroutine, block)
    return coroutine
}

同时,produce发射完成后是会自己关闭的,省的我们自己关闭信道:

override fun onCompletionInternal(state: Any?, mode: Int, suppressed: Boolean) {
    val cause = (state as? CompletedExceptionally)?.cause
    val processed = _channel.close(cause)
    if (cause != null && !processed && suppressed) handleExceptionViaHandler(context, cause)
}

通过jobinvokeOnCompletion实现。

actor

example

val channel: SendChannel<View> = actor(UI) {
    for (number in channel) {
        textView.text = "A new click happend!"
    }
}

button.setOnClickListener {
    launch(CommonPool) {
        channel.send(it)
    }
}

produce相反返回sendChannel

高级用法

fun Node.onClick(action: suspend (MouseEvent) -> Unit) {
    // launch one actor to handle all events on this node
    val eventActor = GlobalScope.actor<MouseEvent>(Dispatchers.Main) {
        for (event in channel) action(event) // pass event to action
    }
    // install a listener to offer events to this actor
    onMouseClicked = EventHandler { event ->
        eventActor.offer(event)
    }
}

我们看这里用了offer而不是send,我们可以把for..in..先简单的写成以下形式:

while(iterator.hasNext()){ //suspend fuction
    val event = iterator.next() //suspend function
    action(event)
}


private suspend fun hasNextSuspend(): Boolean = suspendAtomicCancellableCoroutine sc@ { cont ->
    val receive = ReceiveHasNext(this, cont)
    while (true) {
        if (channel.enqueueReceive(receive)) {
            channel.removeReceiveOnCancel(cont, receive)
            return@sc
        }
        // hm... something is not right. try to poll
        val result = channel.pollInternal()
        this.result = result
        if (result is Closed<*>) {
            if (result.closeCause == null)
                cont.resume(false)
            else
                cont.resumeWithException(result.receiveException)
            return@sc
        }
        if (result !== POLL_FAILED) {
            cont.resume(true)
            return@sc
        }
    }
}        

假设队列里没有东西时,enqueue一个receiveHasNext进行等待。过会解释一下channel的原理。现在只要知道,当有sender.send时,与receive关联的cont就会被调用resume,那么显而易见,当action正在处理时队列中没有receiver,而offer是不会suspend的,因此事件就被抛弃。

conflation事件合并
fun Node.onClick(action: suspend (MouseEvent) -> Unit) {
    // launch one actor to handle all events on this node
    val eventActor = GlobalScope.actor<MouseEvent>(Dispatchers.Main, capacity = Channel.CONFLATED) { // <--- Changed here
        for (event in channel) action(event) // pass event to action
    }
    // install a listener to offer events to this actor
    onMouseClicked = EventHandler { event ->
        eventActor.offer(event)
    }
}

这里我们使用CONFALTED,即合并所有事件,因此接受者永远处理最近一个。原理如下:

result === OFFER_FAILED -> { // try to buffer
    val sendResult = sendConflated(element)
    when (sendResult) {
        null -> return OFFER_SUCCESS
        is Closed<*> -> {
            conflatePreviousSendBuffered(sendResult)
            return sendResult
        }
    }
    // otherwise there was receiver in queue, retry super.offerInternal
}

offer失败时需要suspend等待,(说明还没有接受者或者人家正忙着),插入sendBuffered,同时移除前面已有的sendBuffered

 var prev = node.prevNode
    while (prev is SendBuffered<*>) {
        if (!prev.remove()) {
            prev.helpRemove()
        }
        prev = prev.prevNode
    }

这样永远是最近一个生效。

大概channel原理

其实看abstractChannel会先看到一个queue,这时候显而易见会把它当做是像linkedlist那种塞数据的地方。但其实queue是用来放receive/send node。当队列为空时,send时会先从队列取第一个receiveNode,取不到就suspend,把自己当成sendNode放入;不然就把数据直接交给receiveNode

具体channel实现时,例如ArrayChannel(buffer),会多加一个buffer队列,当队列为空时,send时会先从队列取第一个receiveNode,取不到就放入buffer队列,如果buffer队列满了,把自己当成sendNode放入就suspend;同时把不然就把数据直接交给receiveNode

select

参考

suspend fun selectInsult(john: ReceiveChannel<String>, mike: ReceiveChannel<String>) {
    select<Unit> { //  <Unit> means that this select expression does not produce any result
        john.onReceive { value ->  // this is the first select clause
            println("John says '$value'")
        }
        mike.onReceive { value ->  // this is the second select clause
            println("Mike says '$value'")
        }
    }
}

select可以等任何一个回来,也可以等await:

fun adult(): Deferred<String> = async(CommonPool) {
    // the adult stops the exchange after a while
    delay(Random().nextInt(2000).toLong())
    "Stop it!"
}

suspend fun selectInsult(john: ReceiveChannel<String>, mike: ReceiveChannel<String>,
                         adult: Deferred<String>) {
    select {
        // [..] the rest is like before
        adult.onAwait { value ->
            println("Exasperated adult says '$value'")
        }
    }
}

linux里的select其实类似,(能知道是哪个吗?):

final override val onReceive: SelectClause1<E>
    get() = object : SelectClause1<E> {
        override fun <R> registerSelectClause1(select: SelectInstance<R>, block: suspend (E) -> R) {
            registerSelectReceive(select, block)
        }
    }
    
    
private fun <R> registerSelectReceive(select: SelectInstance<R>, block: suspend (E) -> R) {
    while (true) {
        if (select.isSelected) return
        if (isEmpty) {
            val enqueueOp = TryEnqueueReceiveDesc(select, block as (suspend (E?) -> R), nullOnClose = false)
            val enqueueResult = select.performAtomicIfNotSelected(enqueueOp) ?: return
            when {
                enqueueResult === ALREADY_SELECTED -> return
                enqueueResult === ENQUEUE_FAILED -> {} // retry
                else -> error("performAtomicIfNotSelected(TryEnqueueReceiveDesc) returned $enqueueResult")
            }
        } else {
            val pollResult = pollSelectInternal(select)
            when {
                pollResult === ALREADY_SELECTED -> return
                pollResult === POLL_FAILED -> {} // retry
                pollResult is Closed<*> -> throw recoverStackTrace(pollResult.receiveException)
                else -> {
                    block.startCoroutineUnintercepted(pollResult as E, select.completion)
                    return
                }
            }
        }
    }
}

能看到onReceive是实现SelectCaluse1,同时在selectBuilderImpl环境下:

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

所以会往queueenqueue两个receive节点。

同时能看到如果任何一次select节点获取数据以后:

 when {
    pollResult === ALREADY_SELECTED -> return
    pollResult === POLL_FAILED -> {} // retry
    pollResult is Closed<*> -> throw recoverStackTrace(pollResult.receiveException)
    else -> {
        block.startCoroutineUnintercepted(pollResult as E, select.completion)
        return
    }
}

会调用block.startCoroutineUnintercepted:

/**
 * Use this function to restart coroutine directly from inside of [suspendCoroutine],
 * when the code is already in the context of this coroutine.
 * It does not use [ContinuationInterceptor] and does not update context of the current thread.
 */
internal fun <R, T> (suspend (R) -> T).startCoroutineUnintercepted(receiver: R, completion: Continuation<T>) {
    startDirect(completion) {  actualCompletion ->
        startCoroutineUninterceptedOrReturn(receiver, actualCompletion)
    }
}

之前讲过startCoroutineUnintercepted其实就是function.invoke(),所以就调用block.invoke(select的completion是自己),获得值后通过uCont.resume即可。

onAwait

这个和deferedjob(Support)搞在一起:

private class SelectAwaitOnCompletion<T, R>(
    job: JobSupport,
    private val select: SelectInstance<R>,
    private val block: suspend (T) -> R
) : JobNode<JobSupport>(job) {
    override fun invoke(cause: Throwable?) {
        if (select.trySelect(null))
            job.selectAwaitCompletion(select, block)
    }
    override fun toString(): String = "SelectAwaitOnCompletion[$select]"
}

可以看到当任务成功后,select会被继续进行

broadcast

首先解决一个问题,一个sender多个receiver是怎么处理的。

val channel = Channel<Int>()
launch {
    val value1 = channel.receive()
}
launch {
    val value2 = channel.receive()
}
launch {
    channel.send(1)
}

因为是1vs1消费。只有第一个会收到,因为它插在等待队列的第一个。用broadcast可以保证大家都收到。它维护一个subscribeuser list,所有消费者都能收到channel.sendelement

operation

map

public fun <E, R> ReceiveChannel<E>.map(context: CoroutineContext = Dispatchers.Unconfined, transform: suspend (E) -> R): ReceiveChannel<R> =
    GlobalScope.produce(context, onCompletion = consumes()) {
        consumeEach {
            send(transform(it))
        }
    }

可以实现跟RX一样的操作符,接受者收到后经过转换再进行发送返回最终新的receiveChannel

hot or cold

channelhot的。

When the data is produced by the Observable itself, we call it a cold Observable. When the data is produced outside the Observable, we call it a hot Observable.

Provide abstraction for cold streams

... 这个todo,后续再说。

参考

Even smarter async with coroutine actors

官方文档

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

推荐阅读更多精彩内容