Flow 的 emit 与 tryEmit :它们出现在哪些类型、背压/缓存语义、何时用谁、常见坑

1. 它们分别"属于谁"

  • 冷流(Cold Flow)构建器 :flow { ... } / transform { ... } 的接收者是 FlowCollector

    → 只能用 emit(value) / emitAll(flow)没有 tryEmit)。

  • 热流(Hot Flow)容器

    • MutableStateFlow:有 value、emit(value)、tryEmit(value)

    • MutableSharedFlow:有 emit(value)、tryEmit(value)

callbackFlow/channelFlow 里是 send/trySend(Channel 语义),不是 emit/tryEmit。


2.emit vs tryEmit

总对比(速查)

场景 / 类型 emit(value) tryEmit(value)
Cold:flow {} / transform {} ✅(挂起函数)向下游发射;若下游慢会挂起,天然背压 ❌ 没有
MutableStateFlow 不会挂起(语义等同 value = ...) ✅ 总是返回 true(StateFlow 为"合流/最新值",无容量限制)
MutableSharedFlow ✅ 默认可能挂起(看缓冲/溢出策略) ✅ 快速返回:true=接收成功;false=当前无法接收(见 §4)

口诀:需要背压/按需挂起 → 用 emit;需要"尽力而为、立刻返回" → 用 tryEmit
StateFlow:直接 state.value = new 最高效;emit/tryEmit 只是 API 形态不同,行为等价(不挂起/永远成功)。


3. 冷流里的emit

scss 复制代码
val numbers: Flow<Int> = flow {
    // 只有 collect 才会执行
    for (i in 1..3) {
        delay(100)           // 可挂起
        emit(i)              // 发射给下游;若下游慢,这里会挂起(背压)
    }
}
  • 背压:上游 emit 会等下游处理,除非你在中间加了 buffer() / conflate() 等运算符。
  • 线程:上游用 flowOn(Dispatchers.IO) 切线程;下游在收集端线程。

4.

MutableSharedFlow的背压 & tryEmit是否成功

MutableSharedFlow 构造:

ini 复制代码
val events = MutableSharedFlow<Event>(
    replay = 0,                    // 历史重放条数
    extraBufferCapacity = 0,       // 额外缓冲容量
    onBufferOverflow = BufferOverflow.SUSPEND // 满了时的策略
)

4.1

emit(挂起语义)

  • 默认 (extraBufferCapacity=0 & SUSPEND):当没有订阅者、或下游/缓冲满 → 挂起 等待可用。

  • onBufferOverflow = DROP_OLDEST / DROP_LATEST:永不挂起,会按策略丢弃旧/新元素。

4.2

tryEmit(非挂起语义)

  • 返回 true:本次发送被接受(直接发给订阅者,或进了缓冲/重放区)。

  • 返回 false :当前无法 接受(典型:replay + extraBufferCapacity == 0 且 SUSPEND,且没有订阅者或缓冲满)。

  • 若 onBufferOverflow = DROP_*:一般 总是 true(会按策略丢弃某个元素来腾位)。

特别注意:replay > 0 时,即使没有订阅者 ,tryEmit 也会把值放进重放缓存 → 多为 true。


5.

MutableStateFlow的emit/tryEmit/value

  • value = x 是首选(同步、立即生效)。
  • emit(x) :是 suspend 形态,不会挂起,等价于 value = x。
  • tryEmit(x)总是 true(StateFlow 只有"最新值",没有容量上限的失败场景)。
  • 收集端是"合流(conflated)" :下游慢会跳过中间值,只拿到最新值。

6. 选用建议(实战)

6.1 UI 状态(StateFlow)

kotlin 复制代码
private val _ui = MutableStateFlow(UiState())
val ui: StateFlow<UiState> = _ui

fun onAction() {
    _ui.update { it.copy(loading = true) } // 推荐
    // 或 _ui.value = it.copy(...)
    // 或 viewModelScope.launch { _ui.emit(...) }(等价但多一层协程)
}

理由:状态只关心"最终一致",用合流语义最省心。

6.2 一次性事件(SharedFlow)

kotlin 复制代码
private val _events = MutableSharedFlow<UiEvent>(
    replay = 0, extraBufferCapacity = 64, onBufferOverflow = BufferOverflow.DROP_OLDEST
)
val events: SharedFlow<UiEvent> = _events

fun toast(msg: String) {
    // UI 线程里不想挂起 → tryEmit
    if (!_events.tryEmit(UiEvent.Toast(msg))) {
        // 极端兜底:必要时丢给后台
        viewModelScope.launch { _events.emit(UiEvent.Toast(msg)) }
    }
}

规则

  • 主线程事件优先 tryEmit(),避免意外挂起卡 UI;

  • 需要"保证送达/愿意挂起"等背压 → 用 emit()。

6.3 冷流升温(避免多处收集重复执行)

scss 复制代码
val resultCold: Flow<List<Item>> = flow { emit(api.search(q)) }.flowOn(Dispatchers.IO)

val resultState: StateFlow<List<Item>> =
    resultCold.stateIn(viewModelScope, SharingStarted.WhileSubscribed(5_000), emptyList())
// 之后仅更新 StateFlow 的 value/emit;UI 多处收集不会重复请求

7. 常见误区 & 纠偏

  1. 在主线程用 emit 发送事件 → 可能挂起卡 UI。

    ✅ 改 tryEmit(或给 SharedFlow 配 extraBufferCapacity、DROP_*)。

  2. 把一次性事件放进 StateFlow → 新订阅者会立刻收到"旧事件"。

    ✅ 事件用 SharedFlow(replay=0);需要"上一次也要看到"才调大 replay。

  3. 冷流被多处收集重复打接口

    ✅ 在 VM 里用 stateIn/shareIn 升温并缓存。

  4. 以为 StateFlow.emit 会挂起

    ❌ 它不会挂起,和 value= 等价;避免无谓 launch { _state.emit(x) }。


8. 小示例:三连板(背压/丢/取最新)

scss 复制代码
// 1) 背压缓冲
val f1 = MutableSharedFlow<Int>(replay = 0, extraBufferCapacity = 64)
f1.tryEmit(1) // true,入缓冲

// 2) 丢旧保新(UI 渲染常用)
val f2 = MutableSharedFlow<Int>(
    replay = 0, extraBufferCapacity = 1, onBufferOverflow = BufferOverflow.DROP_OLDEST
)

// 3) 只取最新(下游处理耗时)
f2
  .conflate()          // 或 collectLatest { ... }
  .onEach { render(it) }
  .launchIn(viewModelScope)

总结

  • emit 是挂起式发送:冷流里天然背压;SharedFlow/StateFlow 里依据缓冲策略。
  • tryEmit 是非挂起式发送:立即返回能否接受(StateFlow 总能;SharedFlow 取决于 replay/extraBufferCapacity/onBufferOverflow)。
  • UI 事件 → SharedFlow + tryEmit;UI 状态 → StateFlow + value/update;冷流多处收集 → stateIn/shareIn 升温。
相关推荐
flyliu3 小时前
继承,继承,继承,哪里有家产可以继承
前端·javascript
司宸3 小时前
Cursor 编辑器高效使用与配置全指南
前端
维维酱3 小时前
为什么说 useCallback 实际上是 useMemo 的特例
前端·react.js
王六岁3 小时前
Vue 3 表单验证组合式 API,提供类似 Ant Design Vue Form 的强大表单验证功能
前端·vue.js
机构师3 小时前
<uniapp><日期组件>基于uniapp,编写一个自定义的日期组件
前端·javascript
lypzcgf3 小时前
Coze源码分析-资源库-创建提示词-前端源码
前端·人工智能·typescript·系统架构·开源软件·react·安全架构
Dream it possible!3 小时前
LeetCode 面试经典 150_矩阵_有效的数独(34_36_C++_中等)(额外数组)
leetcode·面试·矩阵
fury_1233 小时前
vue3:el-date-picker三十天改成第二十九天的23:59:59
前端·javascript·vue.js
小周同学@3 小时前
DOM常见的操作有哪些?
前端·javascript