20 | Flow:为什么说Flow是“冷”的?
Flow 就是“数据流”
- 深入了解
- 翻译
- 解释
- 总结
Kotlin协程中的Flow是一种强大而灵活的数据流模型,为异步、并发任务提供了便捷的解决方案。Flow的操作符与集合操作符高度一致,使得学习成本较低,同时还提供了特殊的操作符,专门为Flow设计。在Flow的中间操作符中,onStart和onCompletion是比较特殊的,它们是以操作符的形式存在,但实际上的作用是监听生命周期回调。对于异常处理,可以使用catch操作符来捕获上游和中间操作的异常,而try-catch则更适用于下游异常的捕获。Flow在Kotlin协程中具有重要的地位,为异步任务处理提供了便利且强大的工具。 Flow中的线程切换可以使用flowOn、launchIn、withContext等操作符,其中flowOn和launchIn已经可以满足大部分需求。另外,Flow被认为是“冷”的,因为只有调用终止操作符之后,Flow才会开始工作。Flow还具有“懒”的特性,一次只会处理一条数据,这使得它更加灵活和高效。 总的来说,Kotlin协程中的Flow提供了强大的异步数据流处理能力,同时具有灵活的线程切换和懒加载特性,为开发者提供了便捷且高效的编程工具。文章还介绍了Flow在Android中的实际应用场景,展示了如何利用Flow的操作符设计出可读性非常好的代码。整体而言,Flow是一个重要且值得深入学习的话题,对于想要深入了解Kotlin协程的开发者来说,是一篇不可多得的精华文章。
《朱涛 · Kotlin 编程第一课》,新⼈⾸单¥59
全部留言(21)
- 最新
- 精选
- Paul Shan思考题:flow本身已经提供了线程切换的中间操作符flowOn和launchIn,来确定不同部分的线程边界并优化,withContext要再次切换线程,势必打破flow规划好的线程边界,估计要出错,抛出异常来提前报错。
作者回复: 没错,是这个意思。
2022-03-2412 - 白乾涛以下代码,为啥没有任何日志输出? 而且,为啥程序不会结束? fun main() = runBlocking { withContext(dispatcher) { flow { log("emit"); emit(1) } .flowOn(Dispatchers.Main) .filter { log("filter"); it > 0 } .collect { log("collect") } } }
作者回复: 我在第17讲当中提到过,在非UI平台上,Dispatchers.Main是没有意义的。只有在Android、Swing之类的平台,我们才可以使用Dispatchers.Main。
2022-03-124 - 曾帅关于思考题,翻了一下 emit() 的代码,发现里面有说到这一点。 里面说不允许在 withContext 里 调用 emit() 是因为 emit() 默认不是线程安全的,而且还给出了一种解决方案,那就是使用 channel 来处理。
作者回复: 能从源码中找到证据,很好。
2022-03-103 - Paul Shan原来在使用Rxjava还是Coroutine的时候,我还是支持使用Rxjava的,Flow出来之后,我就倒向Coroutine Flow。
作者回复: 是的,Flow的学习成本会更低一些。
2022-03-242 - 魏全运Flow 跟RxJava 的使用方式太像了
作者回复: 思想是类似的,但Flow的操作符比RxJava还是要少很多的。所以会更容易上手一些。
2022-03-0432 - pengzhaoyang coder发送的数据必须来自同一个协程内,不允许来自多个CoroutineContext,所以默认不能在flow{}中创建新协程或通过withContext()切换协程。如需切换上游的CoroutineContext,可以通过flowOn()进行切换
作者回复: 是的,那么,为什么不允许多个CoroutineContext?
2022-04-01 - Paul ShanFlow也有热的SharedFlow,还支持一对多的服务,我自己的经验是,Channel在具体场景中基本可以被Flow替代,而且更方便更安全。
作者回复: 是的,Channel更像是一个底层的基础工具,不太适合直接拿来用。
2022-03-24 - 梁中华大佬,能结合几个服务端的例子讲讲不,其实协程的主要发挥场景还是在服务端,就像gorouting一样
作者回复: 感谢你的建议,我会考虑的。
2022-03-22 - dawn大佬,为什么下面的代码没法结束 fun main() { val asCoroutineDispatcher = Executors.newSingleThreadExecutor().asCoroutineDispatcher() val scope = CoroutineScope(asCoroutineDispatcher) runBlocking { flow { emit(1f) emit("s") kotlinx.coroutines.delay(2000) emit(2f) emit(3f) // throw NullPointerException() emit(4f) emit(5f) logX("emit") }.filter { logX("filter") it is Float }.onStart { logX("onStart") }.onCompletion { logX("onCompletion") }.catch { logX("catch $it") }.onEach { logX(it) }.launchIn(scope) logX("end") } }
作者回复: 其实是因为:你代码中的Dispatcher对应的线程池没有定义成守护线程。 ``` fun main() { // 请留意 Dispatcher 当中的isDaemon = true val mySingleDispatcher = Executors.newSingleThreadExecutor { Thread(it, "MySingleThread").apply { isDaemon = true } }.asCoroutineDispatcher() val scope = CoroutineScope(mySingleDispatcher) runBlocking { flow { emit(1f) emit("s") kotlinx.coroutines.delay(2000) emit(2f) emit(3f) // throw NullPointerException() emit(4f) emit(5f) logX("emit") }.filter { logX("filter") it is Float }.onStart { logX("onStart") }.onCompletion { logX("onCompletion") }.catch { logX("catch $it") }.onEach { logX(it) }.launchIn(scope) logX("end") } } ```
2022-03-162 - 白乾涛为啥下面代码中的 emit 也执行在 IO 线程? fun main() = runBlocking { val flow = flow { log("emit"); emit(1) } withContext(Dispatchers.IO) { flow.filter { log("filter"); it > 0 } .collect { log("collect") } } }
作者回复: 这很合理,在默认情况下,emit是会使用collect的协程上下文的。
2022-03-123