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

Posted

技术标签:

【中文标题】StateFlowImpl collect有一个while循环,如果我在UI线程上使用它,为啥它不会阻塞UI线程【英文标题】:StateFlowImpl collect has a while loop,If I use it on UI Thread,Why it doesn't block UI ThreadStateFlowImpl collect有一个while循环,如果我在UI线程上使用它,为什么它不会阻塞UI线程 【发布时间】:2022-01-07 21:54:25 【问题描述】:

如果我在启动时使用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)
    

【问题讨论】:

Please don't post images of code 【参考方案1】:

“阻止”和“永不返回”是两个不同的东西。

术语“阻塞”通常是指独占使用线程,阻止它做其他事情(至少在 JVM 上)。

协程允许在不阻塞线程的情况下拥有这样的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(true) 替换为 while(currentCoroutineContext().isActive),以确保在取消协程时停止循环。

什么时候退出循环

现在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 already provides a ready-to-use coroutine scope 在具有生命周期的活动等组件中(请参阅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")
    

【讨论】:

非常感谢您的回复,“阻塞”和“永不返回”我能理解,让我困惑的是不能进行其他操作,比如一些点击事件,因为这样一段时间( true) 在 UI 线程上,在这种情况下,什么时候可以退出这个循环? @summer 你的第一个协程中的while(true) 不包含任何挂起点(仅像Log.d 这样的阻塞调用),因此它有效地阻塞了UI 线程。与StateFlowImpl 的区别在于,在状态流的collect 中有一个暂停的emit() 调用,它将线程让给其他协程。这个循环永远不会结束,除非你取消运行 collect 的协程(例如当你的活动被销毁时)。 @summer 我更新了我的答案,以便根据您编辑的问题更好地解释。我希望这能澄清这一点

以上是关于StateFlowImpl collect有一个while循环,如果我在UI线程上使用它,为啥它不会阻塞UI线程的主要内容,如果未能解决你的问题,请参考以下文章

Unity中W、A、S、D运动,不知道新功能怎么用

Kotlin:如何使用列表强制转换:未经检查的强制转换:kotlin.collections.List<Kotlin.Any?> 到 kotlin.colletions.List<W

Performance Snapshot and collect VMware support log bundle by command

[ABAP]collect

带索引的Ruby数组collect

pymongo 单次插入太慢,尽管 WriteConcern(w=0)