如果我在启动时使用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
,你必须在一个单独的协程中调用它(使用launch
、async
或其他协程构建器(——这就是你在这里要做的。
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")
}
}