'StateFlowImpl collect has a while loop,If I use it on UI Thread,Why it doesn't block UI Thread
If I use while loop on launch,it will keep running,the click event will not execute,eventually lead to ANR. StateFlowImpl collect has a while loop,When will it exit the loop,this is my case:
class MainActivity : AppCompatActivity(), CoroutineScope by MainScope() {
private val TAG = "MainActivity"
val flow = MutableStateFlow(0)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
launch {
while (true) {
Log.d(TAG, "while")
}
}
launch {
flow.collect {
Log.d(TAG, "onCreate: $it")
}
}
}
}
// This is StateFlowImpl
override suspend fun collect(collector: FlowCollector<T>) {
val slot = allocateSlot()
try {
if (collector is SubscribedFlowCollector) collector.onSubscription()
val collectorJob = currentCoroutineContext()[Job]
var oldState: Any? = null // previously emitted T!! | NULL (null -- nothing emitted yet)
while (true) {
val newState = _state.value
collectorJob?.ensureActive()
if (oldState == null || oldState != newState) {
collector.emit(NULL.unbox(newState))
oldState = newState
}
if (!slot.takePending()) {
slot.awaitPending()
}
}
} finally {
freeSlot(slot)
}
}
Solution 1:[1]
"Blocking" and "never returning" are 2 different things.
The term "blocking" usually refers to using the thread exclusively, preventing it from doing other things (at least on the JVM).
Coroutines allow to have such a while(true) without blocking a thread. As long as there are suspension points in the loop, it gives an opportunity for the thread to go execute some other code from another coroutine and later come back.
In the case of
StateFlowImpl, thecollector.emit()call is a suspension point becauseemit()is a suspending function, so at that point the thread can go execute other coroutines.If you don't have a suspension point (as in your first
launch), the loop is indeed blocking the thread because it never yields it to other coroutines. This is what prevents other code from running on UI thread. You can artifically add suspension points in your loop by calling yield:
launch {
while (true) {
Log.d(TAG, "while")
yield() // allow the thread to go execute other coroutines and come back
}
}
You can also run blocking code on other threads than the main thread. This might be more appropriate if you're doing blocking IO or CPU-intensive stuff.
Note that using yield also makes this coroutine cancellable for free. Otherwise you would have to replace while(true) by while(currentCoroutineContext().isActive) to ensure you stop looping when the coroutine is cancelled.
When will it exit the loop
Now a while(true) loop indeed never returns. When you write the caller code, calling collect on StateFlow prevents any following code in the same coroutine from being executed. This is because code is executed sequentially within a coroutine even when suspend functions are involved (it makes it easy to reason about).
If you want to execute this collect concurrently with other code, you have to call it in a separate coroutine (using launch, async, or other coroutine builders) - and this is what you do here.
launch {
flow.collect {
Log.d(TAG, "onCreate: $it")
}
someOtherCode() // unreachable code
}
someOtherCode2() // this is OK
However, the coroutine calling StateFlow.collect never ends by itself, it needs to be cancelled from outside. This is usually controlled via the coroutine scope used to launch the coroutine.
In your case, you're making the activity implement CoroutineScope by MainScope(). This is not advisable, because you don't cancel that scope anywhere. Android already provides a ready-to-use coroutine scope in components such as Activities which have a lifecycle (see lifecycle-runtime-ktx library). It's called lifecycleScope. You should launch your coroutines in this scope so they automatically get cancelled when the activity is destroyed:
import androidx.lifecycle.lifecycleScope
lifecycleScope.launch { // cancelled when the Activity is destroyed
while (true) {
Log.d(TAG, "while")
yield()
}
}
lifecycleScope.launch { // cancelled when the Activity is destroyed
flow.collect {
Log.d(TAG, "onCreate: $it")
}
}
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
| Solution | Source |
|---|---|
| Solution 1 |
