I have a viewModel, which has a property to indicate where is requesting.
If it's true
, then return, otherwise make a new request.
private val _isLoading = MutableStateFlow(false)
val isLoading = _isLoading.asStateFlow()
But it not thread-safe like the doc says:
/** * The current value of this state flow. * * Setting a value that is [equal][Any.equals] to the previous one does nothing. * * This property is **thread-safe** and can be safely updated from concurrent > coroutines without * external synchronization. */
public override var value: T
When test multi times, the log is so confusion and there are more than 1 request be made:
fun testThread() = viewModelScope.launch(Dispatchers.IO) {
LogUtils.i(
"test thread",
"enter fun",
Thread.currentThread().name,
"_isLoading.value :${_isLoading.value}"
)
if (_isLoading.value) {
LogUtils.i(
"test thread",
"return",
Thread.currentThread().name,
"_isLoading.value :${_isLoading.value}"
)
return@launch
}
_isLoading.value = true
LogUtils.i(
"test thread",
"set",
Thread.currentThread().name,
"_isLoading.value :${_isLoading.value}"
)
}
fun test() {
for (i in 0..4) {
testThread()
}
}
And the output is:
17:39:47.277 I
│ args[0] = test thread
│ args[1] = enter fun
│ args[2] = DefaultDispatcher-worker-7
│ args[3] = _isLoading.value :false
└────────────────────────────────────────────────────────────────────────────────────────────────────────────────
17:39:47.278 I
│ args[0] = test thread
│ args[1] = enter fun
│ args[2] = DefaultDispatcher-worker-10
│ args[3] = _isLoading.value :false
└────────────────────────────────────────────────────────────────────────────────────────────────────────────────
17:39:47.282 I
│ args[0] = test thread
│ args[1] = return
│ args[2] = DefaultDispatcher-worker-10
│ args[3] = _isLoading.value :true
└────────────────────────────────────────────────────────────────────────────────────────────────────────────────
17:39:47.282 I
│ args[0] = test thread
│ args[1] = enter fun
│ args[2] = DefaultDispatcher-worker-4
│ args[3] = _isLoading.value :false
└────────────────────────────────────────────────────────────────────────────────────────────────────────────────
17:39:47.283 I
│ args[0] = test thread
│ args[1] = set
│ args[2] = DefaultDispatcher-worker-7
│ args[3] = _isLoading.value :true
└────────────────────────────────────────────────────────────────────────────────────────────────────────────────
17:39:47.286 I
│ args[0] = test thread
│ args[1] = return
│ args[2] = DefaultDispatcher-worker-4
│ args[3] = _isLoading.value :true
└────────────────────────────────────────────────────────────────────────────────────────────────────────────────
17:39:47.286 I
│ args[0] = test thread
│ args[1] = enter fun
│ args[2] = DefaultDispatcher-worker-5
│ args[3] = _isLoading.value :false
└────────────────────────────────────────────────────────────────────────────────────────────────────────────────
17:39:47.288 I
│ args[0] = test thread
│ args[1] = enter fun
│ args[2] = DefaultDispatcher-worker-6
│ args[3] = _isLoading.value :false
└────────────────────────────────────────────────────────────────────────────────────────────────────────────────
17:39:47.292 I
│ args[0] = test thread
│ args[1] = return
│ args[2] = DefaultDispatcher-worker-5
│ args[3] = _isLoading.value :true
└────────────────────────────────────────────────────────────────────────────────────────────────────────────────
17:39:47.295 I
│ args[0] = test thread
│ args[1] = return
│ args[2] = DefaultDispatcher-worker-6
│ args[3] = _isLoading.value :true
└────────────────────────────────────────────────────────────────────────────────────────────────────────────────
I don't know why the output is so weird, about the time and the value.
LogUtil.i
is from https://github.com/Blankj/AndroidUtilCode/blob/master/lib/utilcode/src/main/java/com/blankj/utilcode/util/LogUtils.java
How can I to solve this? And how to make only one request in the same time?
I guess the problem is not with the behavior you are seeing, it is truly expected to happen. Thread-safety is present while the synchronization is missing.
In your case, thread-safety means, that only one thread at a time will be given the permission to emit the update to the state flow. Moreover, that same thread-safety concept is the reason your logs appear weird - only one thread is granted access to writing a log entry to the output stream at a time.
Therefore, for example, DefaultDispatcher-worker-4
writes notification about entering the function and detecting false
value of the state flow only when the output stream is accessible and not withheld by other threads.
While the value is already being updated to true
, the output stream is locked by other workers writing to it. So when comes the turn for worker-4 (and this is moment is absolutely random), the log entry will contain outdated information.
If you want to perform only one operation at a time (including writing the logs and updating the value) you should provide synchronization mechanism. For example a natural solution is to use Mutex
val mutex = Mutex()
fun testThread() = viewModelScope.launch(Dispatchers.IO) {
mutex.withLock {
LogUtils.i(
"test thread",
"enter fun",
Thread.currentThread().name,
"_isLoading.value :${_isLoading.value}"
)
if (_isLoading.value) {
LogUtils.i(
"test thread",
"return",
Thread.currentThread().name,
"_isLoading.value :${_isLoading.value}"
)
return@launch
}
_isLoading.value = true
LogUtils.i(
"test thread",
"set",
Thread.currentThread().name,
"_isLoading.value :${_isLoading.value}"
)
}
}