kotlin - StateFlowImpl collect has a while loop,If I use it on UI Thread,Why it doesn't block UI Thread - TagMerge
3StateFlowImpl collect has a while loop,If I use it on UI Thread,Why it doesn't block UI ThreadStateFlowImpl collect has a while loop,If I use it on UI Thread,Why it doesn't block UI Thread

StateFlowImpl collect has a while loop,If I use it on UI Thread,Why it doesn't block UI Thread

Asked 9 months ago
1
3 answers

"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, the collector.emit() call is a suspension point because emit() 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")
    }
}

Source: link

0

I'm learning Python. My goal is to print out 1 through 5, skipping 3, using a while loop, if statement, and keyword continue . My code below prints out just 1 2, and not 1 2 4 5.
i = 1
while i <= 5:
    if i == 3:
        continue
    print(i)
    i += 1
To fix this you can do it like this, print the value when it is not three and then you don't even need to use continue :
i = 1
while i <= 5:
    if i != 3:
        print(i)
    i += 1
However, a faster and easier solution would be this:
for i in range(1, 5 + 1):
    if i != 3:
        print(i)
Or in the for loop case it would be possible to use continue because the for loop will continue to the next element:
for i in range(1, 5 + 1):
    if i == 3:
        continue
    print(i)
And just because, but you can also use a short (linewise) (and fast too) solution like this one:
print('\n'.join(str(i) for i in range(1, 5 + 1) if i != 3))

Source: link

0

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)
    }
}
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
    }
}
In your case, you're making the activity implement CoroutineScope by MainScope(). This is not advisable, because you don't cancel that scope anywhere. Note that Android already provides a ready-to-use coroutine scope in components such as Activities which have a lifecycle. It's called lifecycleScope. You should launch your coroutines in this scope so they automatically get cancelled when the activity is destroyed:
lifecycleScope.launch {
    while (true) {
        Log.d(TAG, "while")
        yield()
    }
}
lifecycleScope.launch {
    while (true) {
        flow.collect {
            Log.d(TAG, "onCreate: $it")
        }
    }
}
Here's my code:
private var longitude = ""
    private var latitude = ""
In the oncreateView, I have to do a api call with my latitude and longitude data. But the value of latitude and longitude never change!
//location
        fusedLocationProviderClient = LocationServices.getFusedLocationProviderClient(requireActivity())
//        getCurrentLocation

        checkPermissions()

        //CALL API
        val request = Request.Builder()
            .url(" API_PATH?latitude=$latitude&longitude=$longitude")
            .build()
        val client = OkHttpClient()
...

Source: link

Recent Questions on kotlin

    Programming Languages