Kotlin Flow 与“天然背压”(完整示例)

关键词:冷流 + 挂起发射 → 下游慢,emit() 等 。需要"让上游不被完全拖慢"时,用 buffer / conflate / collectLatest / debounce / sample。

3.1 基线示例:下游慢 → 上游 emit() 被背压挂起

Kotlin 复制代码
import kotlinx.coroutines.*
import kotlinx.coroutines.flow.*

fun main() = runBlocking {
  val f = flow {
    repeat(3) { i ->
      println("emit $i @${System.currentTimeMillis()%100000}")
      emit(i)                      // 下游没处理完,这里会挂起等待
    }
  }

  f.collect { v ->
    delay(300)                     // 模拟慢消费者
    println("consume $v @${System.currentTimeMillis()%100000}")
  }
}

预期日志(顺序交替,证明 emit 在等):

Kotlin 复制代码
emit 0 @12345
consume 0 @12648
emit 1 @12649
consume 1 @12953
emit 2 @12954
consume 2 @13257

3.2 buffer(n):在上/下游之间放小仓库,让上游先跑一段

Kotlin 复制代码
fun main() = runBlocking {
  val f = flow {
    repeat(5) { i ->
      println("emit $i")
      emit(i)
    }
  }

  f.buffer(2)              // 上游可先放 2 条再等下游
   .collect { v ->
      delay(300)           // 下游仍慢
      println("consume $v")
   }
}

观察点 :前两次 emit 基本不会等,下游开始消费后才逐步"跟上"。


3.3 conflate():丢弃中间值,只保最新(适合"状态")

Kotlin 复制代码
fun main() = runBlocking {
  val f = flow {
    (0..5).forEach { i ->
      println("emit $i"); emit(i); delay(50) // 上游较快
    }
  }

  f.conflate()            // 只保最新,可能 0 -> 3 -> 5 这样跳
   .collect { v ->
      delay(150)          // 下游更慢
      println("consume $v")
   }
}

预期现象 :consume 会跳号(中间值被合并),只执行最新几次。


3.4 collectLatest{}:新值到来就取消上一次处理(可覆盖任务)

Kotlin 复制代码
fun main() = runBlocking {
  (1..4).asFlow()
    .onEach { println("emit $it") }
    .collectLatest { v ->
      println("start $v")
      try {
        delay(200)        // 模拟耗时处理
        println("done $v")
      } finally {
        println("cancel $v (if not finished)")
      }
    }
}

预期现象 :对 1、2、3 的处理会在新值到来时被取消,最终只看到 done 4。


3.5 debounce()/sample():防抖与抽样(控频)

Kotlin 复制代码
fun main() = runBlocking {
  // 模拟用户输入抖动:每 40ms 一个字符
  val typing = flow {
    "android".forEachIndexed { idx, c ->
      emit("$idx:$c"); delay(40)
    }
  }

  println("---- debounce(100) 只发最后一次稳定输入 ----")
  typing.debounce(100).collect { println("debounce -> $it") }

  println("---- sample(120) 每 120ms 采样一次 ----")
  typing.sample(120).collect { println("sample  -> $it") }
}

预期现象:

  • debounce(100):每段输完后只发最后一个;

  • sample(120):每 120ms 取一个最近值,可能跳过一些输入。


3.6 flowOn(Dispatchers.IO):切换上游线程,但背压语义不变

Kotlin 复制代码
fun main() = runBlocking {
  val f = flow {
    repeat(3) { i ->
      println("emit $i on ${Thread.currentThread().name}")
      emit(i)
    }
  }
  f.flowOn(Dispatchers.IO)        // 上游在 IO
   .collect { v ->
      delay(200)
      println("consume $v on ${Thread.currentThread().name}")
   }
}

要点 :即便上游在 IO 线程,下游慢时 emit() 仍会等待(天然背压没变)。


3.7 回调源的背压(callbackFlow + buffer/conflate)

回调是"推模式",要自己决定承压策略:

  • 无 buffer:trySend 易失败(丢);

  • 用 send:缓冲满会挂起回调线程(主线程慎用)。

Kotlin 复制代码
fun motorAngles(): Flow<Int> = callbackFlow {
  val l = object: AngleListener {
    override fun onAngle(d:Int) { trySend(d).isSuccess } // 非挂起推送
  }
  motor.addListener(l)
  awaitClose { motor.removeListener(l) }
}
  .buffer(64)        // 给回调 64 槽缓冲
  .conflate()        // 只要最新(状态类)
  .flowOn(Dispatchers.IO)

小结(可放在节末的一句话)
  • 天然背压 = 下游慢 → 上游 emit() 挂起等待;

  • 想让上游不被完全拖慢:在链路上加 buffer / conflate / collectLatest / debounce / sample;

  • 回调源用 callbackFlow ,配合 buffer 与 conflate 明确你的承压与合并策略;

  • flowOn(IO) 只换"在哪跑",不改变背压本质。

相关推荐
雪落漂泊2 分钟前
C++算法 二分查找篇(上)
开发语言·c++·算法
传奇开心果编程39 分钟前
【Flutter踩坑排雷】第19课:路由硬编码坑|字符串路由书写错误,页面跳转失败排查
android·flutter·ios·移动开发·跨平台·ui框架·声明式ui
波力海苔夹心脆6751 小时前
C# WPF 资源字典详解:ResourceDictionary、MergedDictionaries 与自定义控件案例
开发语言·c#·wpf·入门教程·resource·资源字典
不会算法的笨小孩1 小时前
异常的使用以及会出现的问题
开发语言·c++·c++11·异常
微小的鱼3 小时前
Linux的渲染与显示系统链路
android·java·linux
Hhy_11074 小时前
《C++深度解构06》手撕 string —— 从底层内存管理到深浅拷贝彻底吃透
c语言·开发语言·c++·学习·visual studio
starzy19904 小时前
深入理解Scala函数定义:从方法到函数的类型跨越
开发语言·scala
starzy19904 小时前
深入理解Scala递归函数:从栈溢出到尾调用优化
开发语言·scala
三*一4 小时前
Mapbox‑GL JS 多边形折点编辑工具:实现原理、功能拆解与踩坑总结
开发语言·javascript·ecmascript
YSL0701244 小时前
C++基础
开发语言·c++