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

相关推荐
Remember_99315 分钟前
MySQL 索引详解:从原理到实战优化
java·数据库·mysql·spring·http·adb·面试
❀͜͡傀儡师16 分钟前
基于大语言模型的简历分析和模拟面试系统
人工智能·语言模型·面试
野生技术架构师32 分钟前
Java 21虚拟线程 vs Kotlin协程:高并发编程模型的终极对决与选型思考
java·开发语言·kotlin
言之。37 分钟前
Kotlin快速入门
android·开发语言·kotlin
Warren9837 分钟前
Pytest Fixture 作用域与接口测试 Token 污染问题实战解析
功能测试·面试·单元测试·集成测试·pytest·postman·模块测试
符哥20081 小时前
Android 权限分类说明
android
June bug1 小时前
软件测试面试常见问答题2
面试·职场和发展
大模型玩家七七1 小时前
安全对齐不是消灭风险,而是重新分配风险
android·java·数据库·人工智能·深度学习·安全
2501_901147831 小时前
PyTorch DDP官方文档学习笔记(核心干货版)
pytorch·笔记·学习·算法·面试
李少兄1 小时前
MySQL 中为时间字段设置默认当前时间
android·数据库·mysql