使用协程launch
执行三个函数
scope.launch {
getPrefsOne()
getPrefsTwo()
getPrefsThree()
}
如果函数正在执行除访问数据存储之外的其他操作,则所有三个函数都按预期完成。但是如果函数正在访问数据存储,
private fun getPrefValueFromDataStore(key: String): Flow<Any> {
val prefsKey = stringPreferencesKey(key)
var dataStore: DataStore<Preferences> = dataStore
val value = dataStore.data.map { preferences ->
preferences[prefsKey] ?: false
}
return value
}
则只调用第一个函数。但是,如果将三个函数放在各自的launch
块中,则这三个函数都将执行。
fun theLauncher() {
scope.launch {
getPrefsOne()
}
scope.launch {
getPrefsTwo()
}
scope.launch {
getPrefsThree()
}
}
为什么,以及如何在一个launch
块中运行这三个函数?
import android.util.Log
import androidx.datastore.core.DataStore
import androidx.datastore.preferences.core.Preferences
import androidx.datastore.preferences.core.emptyPreferences
import androidx.datastore.preferences.core.stringPreferencesKey
import kotlinx.coroutines.*
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.catch
import kotlinx.coroutines.flow.map
import java.io.IOException
class TestPrefs(val dataStore: DataStore<Preferences>) {
private var isPrefsOneEnabled = MutableStateFlow(false)
private var isPrefsTwoEnabled = MutableStateFlow(false)
private var isPrefsThreeEnabled = MutableStateFlow(false)
val job = SupervisorJob()
val scope = CoroutineScope(Dispatchers.IO + job)
private fun getPrefValueFromDataStore(key: String): Flow<Any> {
val prefsKey = stringPreferencesKey(key)
var dataStore: DataStore<Preferences> = dataStore
val value = dataStore.data.catch { exception ->
// dataStore.data throws an IOException when an error is encountered when reading data
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")
}
}
fun theLauncher() {
System.out.println("+++ ### enter theLauncher() Thread: ${Thread.currentThread().id}")
scope.launch {
getPrefsOne()
getPrefsTwo()
getPrefsThree()
}
// scope.launch {
// getPrefsTwo()
// }
// scope.launch {
// getPrefsThree()
// }
System.out.println("+++ --- exit theLauncher() Thread: ${Thread.currentThread().id}")
}
suspend fun getPrefsOne() {
System.out.println("+++ +++ +++ getPrefsOne() Thread: ${Thread.currentThread().id}")
getPrefValueFromDataStore("TEST_PREFS_KEY_1")
.collect {
println("+++ getPrefsOne111111() getPrefValueFromDataStore().collect println got $it")
isPrefsOneEnabled.value = it as Boolean
}
//}
}
suspend fun getPrefsTwo() {
System.out.println("+++ +++ +++ getPrefsTwo() Thread: ${Thread.currentThread().id}")
getPrefValueFromDataStore("TEST_PREFS_KEY_2")
.collect {
println("+++ getPrefsTwo222222() getPrefValueFromDataStore().collect println got $it")
isPrefsTwoEnabled.value = it as Boolean
}
}
suspend fun getPrefsThree() {
System.out.println("+++ +++ +++ getPrefsThree Thread: ${Thread.currentThread().id}")
getPrefValueFromDataStore("TEST_PREFS_KEY_3")
.collect {
println("+++ getPrefsTwo3333333() getPrefValueFromDataStore().collect println got $it")
isPrefsThreeEnabled.value = it as Boolean
}
}
}
调用它和日志两种情况:
val dataStore: DataStore<Preferences> by preferencesDataStore(name = USER_PREFERENCES_NAME, scope = scope)
override fun onCreate(savedInstanceState: Bundle?) {
... ...
val testPrefs = TestPrefs(dataStore)
testPrefs.theLauncher()
}
1. run three functions in its own launch block, all three functions are called:
+++ ### enter theLauncher() Thread: 2
+++ +++ +++ getPrefsOne() Thread: 14581
+++ +++ +++ getPrefsTwo() Thread: 14583:
+++ --- exit theLauncher() Thread: 2
+++ 111 --- exit getPrefValueFromDataStore(TEST_PREFS_KEY_1), ret: TestPrefs$getPrefValueFromDataStore$$inlined$map$1@e6b5b99
+++ 111 --- exit getPrefValueFromDataStore(TEST_PREFS_KEY_2), ret: TestPrefs$getPrefValueFromDataStore$$inlined$map$1@af4ce5e
+++ +++ +++ getPrefsThree Thread: 14582
+++ 111 --- exit getPrefValueFromDataStore(TEST_PREFS_KEY_3), ret: TestPrefs$getPrefValueFromDataStore$$inlined$map$1@9e5470c
+++ getPrefsTwo3333333() getPrefValueFromDataStore().collect println got false
+++ getPrefsTwo222222() getPrefValueFromDataStore().collect println got false
+++ getPrefsOne111111() getPrefValueFromDataStore().collect println got false
2. run three functions in one launch block, only first function is called
+++ ### enter theLauncher() Thread: 2
+++ --- exit theLauncher() Thread: 2
+++ +++ +++ getPrefsOne() Thread: 14611
+++ 111 --- exit getPrefValueFromDataStore(TEST_PREFS_KEY_1), ret: TestPrefs$getPrefValueFromDataStore$$inlined$map$1@9e5470c
+++ getPrefsOne111111() getPrefValueFromDataStore().collect println got false
更新:@Joffery用详细信息和StateFlow的文档链接回答了这个问题。
如果有人遇到类似的问题,这里有一些其他的帖子可能会有所帮助:
A StateFlow never completes, so collecting it is an infinite action. This is explained in the documentation of StateFlow. Coroutines are sequential, so if you call collect on a StateFlow, none of the code after that call in the coroutine will ever be reached.
这里没有得到单个值。你在收集无限的流量。流的目的不是获取一个异步值,而是获取更新。在DataStore
的情况下,我相信它返回一个无限流,它为您提供对首选项的所有进一步更新。
这意味着该流上的.collect { ... }
将无限期挂起。
当您运行单个launch
时,您正在运行单个协同程序,依次执行每个函数。第一个任务需要在执行其他任务之前完成。但是第一个线程卡在一个无限集合上,所以它看起来好像挂起了。
如果你的目标确实是有3个并发的(无限的)事件集合来维护那些isPrefsXEnabled
布尔流,那么它们必须在单独的协程中,因为这就是你表达并发性的方式。