Kotlin协程之flow工作原理

=================================================================

学习 flow 一个绕不开的操作符就是 flowOn 了,以下面示例代码为例, flow 需要在协程中使用,下面的 emit(1) 会在 Dispatchers.Default 指定的线程中执行,而 println(it) 会在父协程所在线程中执行:

flow { emit(1) }.flowOn(Dispatchers.Default).collect { println(it) }

flow {} 的源码在上面已经看过了,就是以 block 代码块为参数创建了一个 SafeFlow 对象,接下来看一下 Flow.flowOn 的逻辑:

public fun Flow.flowOn(context: CoroutineContext): Flow {

checkFlowContext(context)

return when {

// 返回自身 Flow 实例

// 这里我们传入了 Dispatchers.Default, 所以不符合这个条件

context == EmptyCoroutineContext -> this

// SafeFlow 不是该类型,因此也不走这个流程,实际上 FusibleFlow 是当连续多次调用 flowOn 后会创建的 Flow 对象

this is FusibleFlow -> fuse(context = context)

// 逻辑走到这里

else -> ChannelFlowOperatorImpl(this, context = context)

}

}

在上面已经对流程注释了一下,因此上述实例代码转换一下即为: SafeFlow.flowOn.collect {} --> ChannelFlowOperatorImpl.collect {}, 这里注意一下创建 ChannelFlowOperatorImpl 对象时传入的两个参数,第一个 this 指的是之前的 SafeFlow 对象,第二个 context 参数即是我们传入的调度器,它是一个协程上下文

ChannelFlowOperatorImpl.collect 实现在父类 ChannelFlowOperator.collect 中,该方法如果发现传入的 coroutineContext 上下文中没有携带调度器,即我们调用 flowOn 时没有传入 Dispatchers 等调度器,则会直接调用上一层 SafeFlow 的 collect 方法(代码不贴了),否则接着调用父类 ChannelFlow 中的 collect 方法,我们直接看 flowOn 中传入了调度器后的逻辑:

internal abstract class ChannelFlowOperator<S, T>(

@JvmField protected val flow: Flow,

context: CoroutineContext,

capacity: Int,

onBufferOverflow: BufferOverflow

) : ChannelFlow(context, capacity, onBufferOverflow) {

override suspend fun collect(collector: FlowCollector) {

// 判断 coroutineContext 逻辑

// …

super.collect(collector) // 调用父类 ChannelFlow 中方法

}

}

public abstract class ChannelFlow(

// upstream context

@JvmField public val context: CoroutineContext,

// buffer capacity between upstream and downstream context

@JvmField public val capacity: Int,

// buffer overflow strategy

@JvmField public val onBufferOverflow: BufferOverflow

) : FusibleFlow {

override suspend fun collect(collector: FlowCollector): Unit =

coroutineScope {

collector.emitAll(produceImpl(this))

}

public open fun produceImpl(scope: CoroutineScope): ReceiveChannel =

scope.produce(context, produceCapacity, onBufferOverflow, start = CoroutineStart.ATOMIC, block = collectToFun)

}

这里可以看到 ChannelFlowOperatorImpl.collect 最后会走到 collector.emitAll(produceImpl(this)) 生产消费的逻辑,我们分步骤看一下生产和接收的流程。

生产数据


首先看上面 produceImpl 方法:

internal fun CoroutineScope.produce(

context: CoroutineContext = EmptyCoroutineContext,

capacity: Int = 0,

onBufferOverflow: BufferOverflow = BufferOverflow.SUSPEND,

start: CoroutineStart = CoroutineStart.DEFAULT,

onCompletion: CompletionHandler? = null,

@BuilderInference block: suspend ProducerScope.() -> Unit

): ReceiveChannel {

val channel = Channel(capacity, onBufferOverflow)

val newContext = newCoroutineContext(context)

val coroutine = ProducerCoroutine(newContext, channel)

if (onCompletion != null) coroutine.invokeOnCompletion(handler = onCompletion)

coroutine.start(start, coroutine, block)

return coroutine

}

看到这个方法,是不是很熟悉呢?参考之前的 Kotlin之深入理解协程工作原理 的文章可以知道,这里的 produce 方法其实就是启动了一个新的协程,该协程执行的代码块 block 是传入的 collectToFun 参数,接着找 collectToFun 可以发现它会取 ChannelFlowOperator.collectTo 方法:

// ChannelFlowOperator

protected override suspend fun collectTo(scope: ProducerScope) =

// flowCollect 方法实现在子类 ChannelFlowOperatorImpl 中

flowCollect(SendingCollector(scope))

// ChannelFlowOperatorImpl

internal class ChannelFlowOperatorImpl(

flow: Flow,

context: CoroutineContext = EmptyCoroutineContext,

capacity: Int = Channel.OPTIONAL_CHANNEL,

onBufferOverflow: BufferOverflow = BufferOverflow.SUSPEND

) : ChannelFlowOperator<T, T>(flow, context, capacity, onBufferOverflow) {

override suspend fun flowCollect(collector: FlowCollector) =

// 这个 flow 就是上层传入的 SafeFlow 对象

flow.collect(collector)

}

根据之前的解析, flow.collect(collector) 中的 flow 是 SafeFlow 对象,其 collect 方法会执行 SafeFlow 中传入的代码块(即flow {}),这个代码块中调用了 collector.emit(1) 方法(上面代码可以看出此时的 collector 是 SendingCollector 实例),因此我们看看 SendingCollector.emit 方法做了什么:

public class SendingCollector(

private val channel: SendChannel

) : FlowCollector {

override suspend fun emit(value: T): Unit = channel.send(value)

}

于是可以知道 produceImpl 方法就是启动了一个新的协程,然后在协程中执行上层 flow 对象(所以 flowOn 会对它上游的部分起作用)中的代码块(里面调用了 SendingCollector.emit 方法),然后通过 Channel.send 方法把这个 value 发送出去

接收数据


上面看了启动协程并在其内通过 Channel 发送数据的流程,这里看一下数据是怎么接收的,回到最开始的代码,从 collector.emitAll(channel) 开始,这个 channel 参数就是上一节上调用 send 发送数据的那个 channel 对象:

public suspend fun FlowCollector.emitAll(channel: ReceiveChannel): Unit =

emitAllImpl(channel, consume = true)

private suspend fun FlowCollector.emitAllImpl(channel: ReceiveChannel, consume: Boolean) {

ensureActive()

var cause: Throwable? = null

try {

while (true) {

val result = run { channel.receiveCatching() }

if (result.isClosed) {

result.exceptionOrNull()?.let { throw it }

break // returns normally when result.closeCause == null

}

emit(result.getOrThrow())

}

} catch (e: Throwable) {

cause = e

throw e

} finally {

if (consume) channel.cancelConsumed(cause)

}

}

这里可以看到开了一个无限循环,然后通过 Channel 去接收数据,并通过 emit 方法把接收的值发射出去,至于调用这个 emit 方法的 FlowCollector 对象是谁呢?再回到一开始 flow { emit(1) }.flowOn(Dispatchers.Default).collect { println(it) } 示例中最后面调用的 collect 方法,结合上一章 collect 的解析,可以知道这个 FlowCollector 就是通过 collect 方法传入的代码块创建的对象:

public suspend inline fun Flow.collect(crossinline action: suspend (value: T) -> Unit): Unit =

collect(object : FlowCollector {

override suspend fun emit(value: T) = action(value)

})

于是最终在收到数据并 emit 后,会把 value 传递给 collect {} 中的代码块去执行。

多个flowOn


以下面代码为例:

flow { emit(1) }.flowOn(Dispatchers.IO).flowOn(Dispatchers.Main).collect { println(it) }

由之前的解析可以知道首先会调用到这里:

public fun Flow.flowOn(context: CoroutineContext): Flow {

checkFlowContext(context)

return when {

context == EmptyCoroutineContext -> this

this is FusibleFlow -> fuse(context = context)

else -> ChannelFlowOperatorImpl(this, context = context)

}

}

第一次调用 flowOn 时返回的是 ChannelFlowOperatorImpl 对象,查看其继承关系可以知道它实现了 FusibleFlow 接口,因此第二次调用 flowOn 时会走 fuse(context = context) 逻辑:

public abstract class ChannelFlow(

// upstream context

@JvmField public val context: CoroutineContext,

// buffer capacity between upstream and downstream context

@JvmField public val capacity: Int,

// buffer overflow strategy

@JvmField public val onBufferOverflow: BufferOverflow

) : FusibleFlow {

public override fun fuse(context: CoroutineContext, capacity: Int, onBufferOverflow: BufferOverflow): Flow {

val newContext = context + this.context

val newCapacity: Int

// 处理 newCapacity 等计算逻辑

// …

if (newContext == this.context && newCapacity == this.capacity && newOverflow == this.onBufferOverflow)
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数初中级Android工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则近万的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。

img

img

img

img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

如果你觉得这些内容对你有帮助,可以扫码获取!!(备注:Android)

要如何成为Android架构师?

搭建自己的知识框架,全面提升自己的技术体系,并且往底层源码方向深入钻研。
大多数技术人喜欢用思维脑图来构建自己的知识体系,一目了然。这里给大家分享一份大厂主流的Android架构师技术体系,可以用来搭建自己的知识框架,或者查漏补缺;

对应这份技术大纲,我也整理了一套Android高级架构师完整系列的视频教程,主要针对3-5年Android开发经验以上,需要往高级架构师层次学习提升的同学,希望能帮你突破瓶颈,跳槽进大厂;

最后我必须强调几点:

1.搭建知识框架可不是说你整理好要学习的知识顺序,然后看一遍理解了能复制粘贴就够了,大多都是需要你自己读懂源码和原理,能自己手写出来的。
2.学习的时候你一定要多看多练几遍,把知识才吃透,还要记笔记,这些很重要! 最后你达到什么水平取决你消化了多少知识
3.最终你的知识框架应该是一个完善的,兼顾广度和深度的技术体系。然后经过多次项目实战积累经验,你才能达到高级架构师的层次。

你只需要按照在这个大的框架去填充自己,年薪40W一定不是终点,技术无止境

《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》,点击传送门即可获取!

927)]

对应这份技术大纲,我也整理了一套Android高级架构师完整系列的视频教程,主要针对3-5年Android开发经验以上,需要往高级架构师层次学习提升的同学,希望能帮你突破瓶颈,跳槽进大厂;

最后我必须强调几点:

1.搭建知识框架可不是说你整理好要学习的知识顺序,然后看一遍理解了能复制粘贴就够了,大多都是需要你自己读懂源码和原理,能自己手写出来的。
2.学习的时候你一定要多看多练几遍,把知识才吃透,还要记笔记,这些很重要! 最后你达到什么水平取决你消化了多少知识
3.最终你的知识框架应该是一个完善的,兼顾广度和深度的技术体系。然后经过多次项目实战积累经验,你才能达到高级架构师的层次。

你只需要按照在这个大的框架去填充自己,年薪40W一定不是终点,技术无止境

《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》,点击传送门即可获取!
  • 30
    点赞
  • 25
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: Kotlin 程中的 delay 函数是基于线程抢占实现的,它会暂停当前程的执行,并将当前程的执行权让给其他程。当 delay 的时间到了之后,调度器会再次调度这个程,并将其加入执行队列。 举个例子,假设有一个程 A,它调用了 delay(1000L),表示暂停 1 秒钟。当 A 程调用 delay 时,调度器会将 A 程的执行权让给其他程,并将 A 程加入等待队列中。1 秒钟之后,调度器会从等待队列中取出 A 程,并将其加入执行队列中。 总的来说,delay 函数的原理就是通过线程抢占来实现程的暂停和唤醒的。 ### 回答2: Kotlin程中的delay函数是一个挂起函数,它可以让程暂停一段指定的时间,然后再继续执行。delay函数的原理是利用了程的挂起和恢复机制。 当我们在程中调用delay函数时,程会通过将自己的状态保存起来,然后释放执行线程。在指定的时间间隔过后,程会被恢复,并继续执行。这样,我们可以实现在程中暂停一段时间而不会阻塞线程的效果。 具体实现上,delay函数内部使用了定时器来实现暂停的功能。它会创建一个定时任务,在指定的时间间隔过后触发恢复程的操作。当程被恢复后,它会继续从上一次暂停的地方开始执行。 需要注意的是,delay函数只能在程中使用,而不能在普通的线程中使用。这是因为程的挂起和恢复功能是基于程的调度器来实现的,而普通的线程并没有这样的机制。 总之,delay函数是Kotlin程中用来控制程的暂停和恢复的重要函数之一。它通过利用程的挂起和恢复机制,以及定时器实现了在程中暂停一段时间的效果,从而提高了并发编程的效率和易用性。 ### 回答3: Kotlin程是一种轻量级的并发编程框架,而`delay`是其中一种常用的程构造器。`delay`的原理是通过暂停当前程一段时间来模拟延迟操作。 在Kotlin中,程是基于挂起函数(Suspending Function)实现的。挂起函数是指可以中断执行,并在某个时间点继续执行的函数。`delay`函数就是一个挂起函数,它的作用是中断当前程的执行,并在指定的延迟时间后继续执行。 具体实现原理如下: 1. 当调用`delay`时,会创建一个`Delay`对象,用于管理当前程的延迟操作; 2. `Delay`对象会将延迟时间记录下来,并创建一个新的挂起点(Suspend Point); 3. 当前程在遇到挂起点时,会中断执行并将控制权交给`Delay`对象; 4. `Delay`对象会通过底层的调度机制,将当前程从线程池中的工作线程中切换到等待队列中; 5. 等待队列中的程会按照延迟时间的顺序排列,当延迟时间到达时,程会从等待队列中出队,被重新放回工作线程中执行; 6. 当程继续执行时,`delay`函数返回,并可以继续执行下面的代码。 总而言之,`delay`函数利用挂起函数的特性,将当前程暂停一段时间,然后再继续执行。通过这种方式,可以方便地实现程的延迟操作,而无需阻塞线程。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值