StateFlowImpl collect 有一个 while 循环,如果我在 UI 线程上使用它,为什么它不阻塞 UI 线程



如果我在启动时使用while循环,它将继续运行,点击事件将不会执行,最终导致ANR。StateFlowImpl collect有一个while循环,它什么时候退出循环,这是我的情况:

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)
}
}

"阻塞";以及";永不返回";是两种不同的东西。

术语";"阻塞";通常指的是专门使用线程,防止它做其他事情(至少在JVM上(。

Coroutines允许在不阻塞线程的情况下拥有这样的while(true)只要循环中有暂停点,线程就有机会执行另一个协程中的其他代码,然后再返回。

  • StateFlowImpl的情况下,collector.emit()调用是挂起点,因为emit()是一个挂起函数,所以在这一点上线程可以执行其他协程。

  • 如果您没有挂接点(如在第一个launch中(,那么循环实际上阻塞了线程,因为它从不将线程交给其他协程。这就是阻止其他代码在UI线程上运行的原因。您可以通过调用yield:在循环中人为添加挂接点

launch {
while (true) {
Log.d(TAG, "while")
yield() // allow the thread to go execute other coroutines and come back
}
}

您还可以在主线程之外的其他线程上运行阻塞代码。如果你正在做阻塞IO或CPU密集型的事情,这可能更合适。

请注意,使用yield还可以免费取消此协同程序。否则,您将不得不用while(currentCoroutineContext().isActive)替换while(true),以确保在取消协程时停止循环。

何时退出循环

现在while(true)循环确实永远不会返回。编写调用方代码时,在StateFlow上调用collect会阻止同一协程中的任何后续代码执行。这是因为即使涉及到挂起函数,代码也会在协同程序中按顺序执行(这很容易推理(。

如果你想与其他代码同时执行这个collect,你必须在一个单独的协程中调用它(使用launchasync或其他协程构建器(——这就是你在这里要做的。

launch {
flow.collect {
Log.d(TAG, "onCreate: $it")
}
someOtherCode() // unreachable code
}
someOtherCode2() // this is OK

然而,调用StateFlow.collect的协同程序本身永远不会结束,它需要从外部取消。这通常是通过用于启动协同程序的协同程序范围来控制的。

在您的案例中,您正在使活动实现CoroutineScope by MainScope()。这是不可取的,因为您不会在任何地方取消该范围。Android已经在具有生命周期的组件(如Activities(中提供了一个现成的协同程序范围(请参阅lifecycle-runtime-ktx库(。它被称为lifecycleScope。你应该在这个范围内启动你的协同程序,这样当活动被破坏时它们就会自动被取消:

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")
}
}

相关内容

  • 没有找到相关文章

最新更新