Kotlin协程launch块调用DataStore仅执行首个函数的原因与解决
Kotlin协程中单个launch块执行DataStore相关函数仅首个生效的问题与解决
问题场景
在Kotlin协程开发中,尝试在单个launch块内顺序执行三个读取DataStore的函数:
scope.launch { getPrefsOne() getPrefsTwo() getPrefsThree() }
当函数不涉及DataStore操作时,三者均可正常完成;但当函数通过DataStore读取配置时,仅getPrefsOne()会被执行。若将三个函数分别放入独立的launch块,则全部能正常运行。
相关核心代码示例:
private fun getPrefValueFromDataStore(key: String): Flow<Any> { val prefsKey = stringPreferencesKey(key) var dataStore: DataStore<Preferences> = dataStore val value = dataStore.data.catch { exception -> println("+++ !!! exp $exception in getPrefValueFromDataStore($key)") if (exception is IOException) { Log.e("+++", "+++ !!! Error reading preferences.", exception) emit(emptyPreferences()) } else { throw exception } } .map { preferences -> preferences[prefsKey] ?: false } return value.also{ println("+++ 111 --- exit getPrefValueFromDataStore($key), ret: $it") } } suspend fun getPrefsOne() { println("+++ +++ +++ getPrefsOne() Thread: ${Thread.currentThread().id}") getPrefValueFromDataStore("TEST_PREFS_KEY_1") .collect { println("+++ getPrefsOne111111() getPrefValueFromDataStore().collect println got $it") isPrefsOneEnabled.value = it as Boolean } } // getPrefsTwo、getPrefsThree结构与getPrefsOne一致
日志对比:
- 独立launch块时:三个函数均被执行,各自输出collect结果
- 单个launch块时:仅执行
getPrefsOne(),后续函数无任何日志输出
原因分析
dataStore.data返回的是永不终止的冷流:它会持续监听DataStore的变化,collect操作会一直挂起当前协程,不会自动完成。- 单个
launch块内的协程是顺序执行的:getPrefsOne()中的collect会一直占用当前协程,导致后续的getPrefsTwo()和getPrefsThree()永远无法被调度执行。 - 独立
launch块是开启了三个并行协程:每个协程独立处理自己的collect操作,互不阻塞,因此都能正常运行。
解决方案
方案1:仅读取一次数据(无需监听变化)
如果只需要获取DataStore的当前值,不需要持续监听后续变更,可以用first()替代collect,Flow会在获取第一个值后立即终止,不会挂起协程:
suspend fun getPrefsOne() { println("+++ +++ +++ getPrefsOne() Thread: ${Thread.currentThread().id}") val value = getPrefValueFromDataStore("TEST_PREFS_KEY_1").first() as Boolean println("+++ getPrefsOne111111() got $value") isPrefsOneEnabled.value = value }
修改后,三个函数可以在单个launch块内顺序执行完成。
方案2:持续监听数据变化(保留collect)
如果必须持续监听DataStore的变更,可在单个launch块内开启子协程并行执行每个collect:
scope.launch { launch { getPrefsOne() } launch { getPrefsTwo() } launch { getPrefsThree() } }
这种方式和使用三个独立launch块的效果一致,所有collect操作并行运行,互不阻塞。
额外优化:修正DataStore函数的冗余问题
原代码中getPrefValueFromDataStore存在冗余变量声明,同时可以优化返回类型的精确性:
private fun getPrefValueFromDataStore(key: String): Flow<Boolean> { val prefsKey = stringPreferencesKey(key) return dataStore.data .catch { exception -> println("+++ !!! exp $exception in getPrefValueFromDataStore($key)") if (exception is IOException) { Log.e("+++", "+++ !!! Error reading preferences.", exception) emit(emptyPreferences()) } else { throw exception } } .map { preferences -> preferences[prefsKey]?.toBoolean() ?: false } .also { println("+++ 111 --- exit getPrefValueFromDataStore($key), ret: $it") } }
内容的提问来源于stack exchange,提问作者lannyf
相关产品推荐
相关产品推荐

