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

相关推荐
Yang-Never3 分钟前
ADB -> pull指令推送电脑文件到手机上
android·adb·android studio
李新_5 分钟前
我们封装了哪些好用的Flutter Mixin
android·flutter
mysql学习中6 分钟前
数仓面试内容
面试·职场和发展
帅次19 分钟前
Flutter Expanded 与 Flexible 详解
android·flutter·ios·小程序·webview
流浪汉kylin22 分钟前
Android手机如何腾出存储空间
android
Java知识库1 小时前
Java BIO、NIO、AIO、Netty面试题(已整理全套PDF版本)
java·开发语言·jvm·面试·程序员
0wioiw01 小时前
Kotlin基础(①)
android·开发语言·kotlin
西瓜本瓜@1 小时前
在 Android 中实现通话录音
android·java·开发语言·学习·github·android-studio
JiangJiang1 小时前
🚀 Vue 人如何玩转 React 自定义 Hook?从 Mixins 到 Hook 的华丽转身
前端·react.js·面试
lianghj2 小时前
前端高手必备:深度解析高频场景解决方案与性能优化实战
前端·javascript·面试