Kotlin 流 Flow

挂起函数可以异步地返回一个值,而对于返回多个值 ,可以使用流,使用 emit(x) 发射多个值,
collect { } 来收集值。

默认 流是冷的,只有 收集(collect) 时才会执行。

1. 流的创建

  1. flow {} 生成流,emit(x) 来发射值;
  2. xxx.asFlow() 集合转成Flow;
  3. flowOf(1, 2, 3) 生成固定值的流。
1.1 flow {}

flow {} 里的 发射(emissions)默认是可取消的 ,对应 SafeFlow,继承自 AbstractFlow

kotlin 复制代码
public abstract class AbstractFlow<T> : Flow<T>, CancellableFlow<T> {

    public final override suspend fun collect(collector: FlowCollector<T>) {
        val safeCollector = SafeCollector(collector, coroutineContext)
        try {
            collectSafely(safeCollector)
        } finally {
            safeCollector.releaseIntercepted()
        }
    }

}

对应 emit() 方法,就是 SafeCollector.emit() 内部 调用了 currentContext.ensureActive() 做 取消检查。

而 其他两种,默认是 不可取消 ,使用 cancellable() 操作符可取消。

2. 流的操作符

2.1 中间操作符
  1. map
  2. filter
  3. transform
2.2 结束操作符
  1. collect {}
  2. toList()toSet() 收集到集合
  3. first()single()
  4. reduce()fold() 合并值

3. 流的上下文

默认 流 执行在 和 收集者(Collector) 相同的 上下文。

更改流的发射的上下文,必须使用 flowOn,而不是在 flow {} 中使用 withContext()

kotlin 复制代码
flow {
    ...
}.flowOn(Dispatchers.Default)

4. 缓冲

当 Flow emit 生产者速度 大于 collector 消费者速度时。

  1. buffer() 并发地执行 发射 和 收集,而不是 顺序执行(发射 收集 再 发射 收集);
  2. conflate() 丢弃中间值,取最新发射的值;
  3. collectLatest { } 收集最新的值,但 如果 发射新值,会 取消 慢的收集。
kotlin 复制代码
simple()
    .collectLatest { value -> // cancel & restart on the latest value
        println("Collecting $value") 
        delay(300) // pretend we are processing it for 300 ms
        println("Done $value") 
    } 

说明:会执行所有 Collecting,但是 因为慢处理,会被取消。

结果:

复制代码
Collecting 1
Collecting 2
Collecting 3
Done 3

https://kotlinlang.org/docs/flow.html#processing-the-latest-value

5. 流的组合

  1. zip 1对1的 组合
  2. combine 每次上游更新,就会重新计算。两个流 生产速度不一样时,就会 不同的对应组合。更新值即组合

举例:

zip 组合值: 1->one, 2->two 3->three,

而 combine 组合,则可能 1->one 2->one 这样只有一方流 发射值,就会调用计算。

6. flatten 展平

对于 Flow 又对应 Flow<T> 任务,这时候对于 Flow<Flow<T>> 需要展开Flow<T>。场景就是 一序列 对应 请求任务。

kotlin 复制代码
fun requestFlow(i: Int): Flow<String> = flow {
    emit("$i: First")
    delay(500) // wait 500 ms
    emit("$i: Second")
}

(1..3).asFlow().map { requestFlow(it) }
  1. flatMapConcat 按顺序,流完成后,才接着下一个流
  2. flatMapMerge 支持并发 地处理,流 则 出现并发交错地收集值,concurrency 设置并发数
  3. flatMapLatest 处理最新的流 ,当新的流发射值时,取消之前的流

7. 异常

  1. try { flow.collect {} } catch (e: Exception) { } 处理异常,包含收集器里代码异常;
  2. flow {}.catch { }.collect { } 处理 上游异常,但不会处理 下游 异常;
  3. flow {}.onEach { }.collect() 处理上下游异常。
7.1. try/catch 全部捕获
kotlin 复制代码
try {
    simple()
        .collect {
            println("value: $it")
        }
} catch (e: Exception) {
    // 捕获了 flow发射代码块、中间操作符 和 结束操作符 的所有异常
}
7.2. catch 操作符
kotlin 复制代码
simple()
    .catch { e ->
        // catch 捕获上面的异常,但 不处理 下游 和 结束操作符 的 异常
        println("exception: $e") 
    }
    .collect {
        // 如果 这里异常,则不会被捕捉
        println("value: $it")
    }
7.3. 声明式捕获

如果想 捕获 结束操作符的异常,需要 声明式地捕捉。把 collect 的代码部分 上移到 onEach 中,使用无参的 collect() 收集:

kotlin 复制代码
simple()
    .onEach {
        check(it < 2)
        println("value: $it")
    }
    .catch { e -> 
        println("exception: $e")
    }
    .collect()

8. 完成

  1. try/finnaly 在结束后处理
  2. flow {}.onCompletion { cause -> } 处理
kotlin 复制代码
flow {

}.onCompletion { cause ->
    // 完成回调,cause 是空 表示 正常完成
    if (cause == null) {
        println("success")
    }
}

9. 取消

  1. onEach 时检测
  2. cancel() 在收集时,调用取消
  3. flow {}.cancellable() 设置flow可取消
kotlin 复制代码
// 不加 cancellable() 不会 做取消检查,导致完成收集后 才 报异常
// cancellable() 则会 及时取消
// flowOf(1, 2, 3)
listOf(1, 2, 3).asFlow()
//  .cancellable()
    .collect {
        if (it > 1) {
            cancel()
        }
        println("value: $it")
    }

cancellable()的实现:

  • CancellableFlowImpl
kotlin 复制代码
public fun <T> Flow<T>.cancellable(): Flow<T> =
    when (this) {
        is CancellableFlow<*> -> this // Fast-path, already cancellable
        else -> CancellableFlowImpl(this)
    }

private class CancellableFlowImpl<T>(private val flow: Flow<T>) : CancellableFlow<T> {
    override suspend fun collect(collector: FlowCollector<T>) {
        flow.collect {
            currentCoroutineContext().ensureActive()
            collector.emit(it)
        }
    }
}

文档

相关推荐
Scott9999HH12 小时前
【IIoT流量实战】蒸汽管道阀门全关却仍有流量?用 Python 实现涡街信号 FFT 频谱分析与温压全补偿积算网关,深度拆解靠谱的涡街流量计厂家硬核技术标准
开发语言·python
码智社13 小时前
AES加密原理详解及Java实现加解密实战
java·开发语言
AI云海13 小时前
python 列表、元组、集合和字典
开发语言·python
萧瑟余晖14 小时前
JDK 26 新特性详解
java·开发语言
马优晨15 小时前
Freemarker 完整讲解(后端 Java 模板引擎)
java·开发语言·freemarker·freemarker 完整讲解·freemarker模板引擎
人邮异步社区17 小时前
怎么把C语言学到精通?
c语言·开发语言
心平气和量大福大17 小时前
C#-WPF-控件-TextBox 数据绑定
开发语言·c#·wpf
ttwuai18 小时前
Cursor 生成 CRUD 后,Go 后台接口别只测 200:JWT、RBAC 和 tenant_id 怎么验
开发语言·后端·golang
এ慕ོ冬℘゜18 小时前
前端基础:什么是时间戳?JS获取时间戳三种方法与实战用途
开发语言·前端·javascript
执明wa18 小时前
LayoutInflater详解: XML是如何变成View的?
android·xml·开发语言·android studio