关于 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.这次为了参加掘金的活动,破天荒,这么快总结完,草稿都已经存了一年了...欢迎大家 点赞+收藏+批评~

相关推荐
啊森要自信5 分钟前
【MySQL 数据库】MySQL索引特性(二)页目录&&(B和B+树)&&(非)聚簇索引 && 索引操作
android·数据库·sql·mysql·adb·数据库架构
UrbanJazzerati25 分钟前
在Apex中访问自定义标签:静态与动态方法详解
面试·saas
穷人小水滴38 分钟前
Android 运行 deno 的新方法 (3): Termux 胖喵安初
android·linux
PineappleCoder38 分钟前
防抖 vs 节流:高频事件的 “性能优化双雄” 怎么用?
前端·javascript·面试
ZzMemory42 分钟前
CSS 命名太乱?BEM 规范帮你一键搞定,代码清爽到飞起!
前端·css·面试
穷人小水滴1 小时前
7 天充电宝计划 (小实验)
android·linux
前端小巷子1 小时前
Vue 2 渲染链路剖析
前端·vue.js·面试
jzlhll1238 小时前
android MVC/MVP/MVVM/MVI架构发展历程和编写范式
android·架构
安卓开发者8 小时前
Android ConstraintLayout 使用详解
android
CV资深专家12 小时前
Android 基础入门学习目录(持续更新)
android