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
,您必须在单独的协程中调用它(使用launch
、async
或其他协程构建器) - 和这就是你在这里所做的。
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线程的主要内容,如果未能解决你的问题,请参考以下文章
Kotlin:如何使用列表强制转换:未经检查的强制转换:kotlin.collections.List<Kotlin.Any?> 到 kotlin.colletions.List<W
Performance Snapshot and collect VMware support log bundle by command