关于 MutableSharedFlow 的 tryEmit 和 emit 争议说法

这篇文章需要对 kotlin 的 flow 有一定了解,扫盲可以先看这个文章 Flow 扫盲

这是 MutableSharedFlow 关于 tryEmit 和 emit 方法的定义。

kotlin 复制代码
override suspend fun emit(value: T)

public fun tryEmit(value: T): Boolean
  • 争议说法1:

    tryEmit 返回 true 代表发送成功,返回false 代表发送失败。

  • 争议说法2:

    tryEmit 是一个非挂起方法,这也是 tryEmit 和 emit 方法的根本区别。

先摆源码,然后再问题:

kotlin 复制代码
override fun tryEmit(value: T): Boolean {
    var resumes: Array<Continuation<Unit>?> = EMPTY_RESUMES
    val emitted = synchronized(this) {
        if (tryEmitLocked(value)) {
            resumes = findSlotsToResumeLocked(resumes)
            true
        } else {
            false
        }
    }
    for (cont in resumes) cont?.resume(Unit)
    return emitted
}
kotlin 复制代码
@Suppress("UNCHECKED_CAST")
private fun tryEmitLocked(value: T): Boolean {
    if (nCollectors == 0) return tryEmitNoCollectorsLocked(value) // always returns true
    if (bufferSize >= bufferCapacity && minCollectorIndex <= replayIndex) {
        when (onBufferOverflow) {
            BufferOverflow.SUSPEND -> return false // will suspend
            BufferOverflow.DROP_LATEST -> return true // just drop incoming
            BufferOverflow.DROP_OLDEST -> {} // force enqueue & drop oldest instead
        }
    }
    enqueueLocked(value)
    bufferSize++ // value was added to buffer
    if (bufferSize > bufferCapacity) dropOldestLocked()
    if (replaySize > replay) { // increment replayIndex by one
        updateBufferLocked(replayIndex + 1, minCollectorIndex, bufferEndIndex, queueEndIndex)
    }
    return true
}

问题

q1:使用 tryEmit 能发送成功么?

kotlin 复制代码
private val _testFlow = MutableSharedFlow<Boolean>()  
val testFlow: SharedFlow<Boolean> = _testFlow  
  
fun sendNew(newDat:Boolean){  
    _testFlow.tryEmit(newDat)  
}

fun registerFlowObser(){
    launchCoroutine {  
        testFlow.collect {  
            Log.e("zly", "testFlow--$it")  
        }  
    }
}

这里是发送失败的,bufferSize >= bufferCapacity && minCollectorIndex <= replayIndex 条件下,判断为 BufferOverflow.SUSPEND ,然后 return false。

「曾经组内同学就这么定义的 flow 并且使用的 tryEmit,找了很久 才知道原来 数据根本就没发出去...)」

所以 tryEmit 并不适用于这种场景。_testFlow 这种定义 只能使用 emit 方法。

q2:返回true,就是发送成功了么

kotlin 复制代码
private val _testFlow = MutableSharedFlow<String>(extraBufferCapacity = 1, onBufferOverflow = BufferOverflow.DROP_OLDEST)  
val testFlow: SharedFlow<String> = _testFlow 
  
fun sendNew(newDat:String){  
    val result = _testFlow.tryEmit(newDat)  
    Log.e("zly","send ----- $newDat -- result=$result")
}

fun registerFlowObser(){
    launchCoroutine {  
        testFlow.collect {  
            delay(2000)
            Log.e("zly", "testFlow--$it")  
        }  
    }
}

连续快速执行 sendNew 方法,每次 result 都是 true,通过源码 BufferOverflow.DROP_OLDEST -> {} 可以看出 使用这个策略的case 下,始终都会返回true,但数据不一定能够正确处理。所以「发送成功」!= 「发送成功且被collector 成功接收」

q3:emit 和 tryEmit 方法区别

kotlin 复制代码
override suspend fun emit(value: T) {  
    if (tryEmit(value)) return // fast-path  
    emitSuspend(value)  
}

emit 能够确保数据的发送,且是先看调用 tryEmit 方法,发送失败,则再调用 emit 确保数据发送成功。

q4: curValue 的所有引用都在如下代码中,是否需要 Volatile 修饰?

kotlin 复制代码
@Volatile
private var curValue = false

launchCoroutine {
//playStateUiState 是一个 stateflow
    playStateUiState.collect {
        if (curValue != true) {
            // 执行具体业务逻辑
        }
        curValue = it.needForceUpdate
    }
}

这是我最近写的需求中涉及到的代码,在我写系统性看 Java 和 Kotlin 中的锁这篇文章的时候,注意到这段代码,才发现手欠写了 @Volatile 这个注解,其实这种 case 下是完全没必要加的,因为 curValue 的读取和赋值都在 collect 代码块中,顺序执行,不存在竞争条件。

总结

如何选择使用 tryEmit 还是 emit 方法?

  • 根据场景来选择使用tryEmit 还是 emit。eg:直播间场景的评论区展示的评论适合使用 tryEmit,在高频case下允许数据丢失
  • 根据选择的方法,定义合适的 MutableSharedFlow

eg:直播间的评论区消息的定义

kotlin 复制代码
private val _highFrequencyFlow by lazy { MutableSharedFlow<Bean>(0, HIGH_FREQUENCY_MAX, BufferOverflow.DROP_OLDEST) }  
val highFrequencyFlow :SharedFlow<Bean> = _highFrequencyFlow

fun sendChat(event:Bean){
    _highFrequencyFlow.tryEmit(event)
}

tryEmit 返回true,不代表数据被接收者成功接收。

ps.这次为了参加掘金的活动,破天荒,这么快总结完,草稿都已经存了一年了...欢迎大家 点赞+收藏+批评~

相关推荐
HaiXCoder2 分钟前
AndroidAutoSize 框架原理分析与核心问题
android
fengci.37 分钟前
CTF+随机困难题目
android·开发语言·前端·学习·php
Le_ee1 小时前
SWPUCTF 2025 秋季新生赛wp2
android
搬砖码1 小时前
同源多标签页通信 4 种方案,从入门到生产环境
前端·面试
野生技术架构师1 小时前
我总结了这份2026最新版Java面试题库(背完这一套就够了)
java·开发语言·面试
装杯让你飞起来啊2 小时前
混合练习 —— 猜数字游戏
windows·游戏·kotlin
张元清2 小时前
SSR 状态管理陷阱:defineStore vs defineContextStore
前端·javascript·面试
装杯让你飞起来啊2 小时前
Kotlin 条件判断 if / when 与智能转换 smart cast
开发语言·python·kotlin
pengyu2 小时前
【Kotlin 协程修仙录 · 金丹境 · 初阶】 | 并发艺术:async/await 与并发组合的优雅之道
android·kotlin
神三元3 小时前
最近半年,我做了个 AI-Native 的 Agent 从零到进阶教程
前端·javascript·面试