onEach更改StateFlow中的调度程序(kotlin协程)
想象一下下面的自包含测试用例onEach更改StateFlow中的调度程序(kotlin协程),kotlin,kotlin-coroutines,kotlin-coroutines-flow,Kotlin,Kotlin Coroutines,Kotlin Coroutines Flow,想象一下下面的自包含测试用例 @Test fun `stateFlow in GlobalScope`() = runBlockingTest { suspend fun makeHeavyRequest(): String { return "heavy result" } val flow1 = flowOf(Unit) .map { makeHeavyRequest() } .onEach {
@Test
fun `stateFlow in GlobalScope`() = runBlockingTest {
suspend fun makeHeavyRequest(): String {
return "heavy result"
}
val flow1 = flowOf(Unit)
.map { makeHeavyRequest() }
.onEach { logThread("1: before flowOn") }
.flowOn(testDispatcher)
.stateIn(GlobalScope, SharingStarted.Lazily, "init state")
val flow2 = flowOf(Unit)
.map { makeHeavyRequest() }
.onEach { logThread("2: before flowOn") }
.flowOn(testDispatcher)
.stateIn(GlobalScope, SharingStarted.Lazily, "init state")
.onEach { logThread("2: after stateIn") }
val flow3 = flowOf(Unit)
.map { makeHeavyRequest() }
.onEach { logThread("3: before flowOn") }
.flowOn(testDispatcher)
.onEach { logThread("3: after flowOn") }
.stateIn(GlobalScope, SharingStarted.Lazily, "init state")
flow1.test {
assertEquals("heavy result", expectItem())
cancelAndIgnoreRemainingEvents()
}
flow2.test {
assertEquals("heavy result", expectItem())
cancelAndIgnoreRemainingEvents()
}
flow3.test {
assertEquals("heavy result", expectItem())
cancelAndIgnoreRemainingEvents()
}
}
运行它的效果将是:
Thread (1: before flowOn): Thread[main @coroutine#2,5,main]
Thread (2: before flowOn): Thread[main @coroutine#3,5,main]
Thread (2: after stateIn): Thread[main @coroutine#6,5,main]
Thread (3: before flowOn): Thread[DefaultDispatcher-worker-1 @coroutine#8,5,main]
Thread (3: after flowOn): Thread[DefaultDispatcher-worker-1 @coroutine#4,5,main]
org.opentest4j.AssertionFailedError:
Expected :heavy result
Actual :init state
在flow3中,将onEach放在flowOn和stateIn之间会完全改变调度器并将结果弄乱。为什么会这样?您应该使用MainScope而不是GlobalScope。
全局范围是一个不受控制的范围
您可以在协同程序中找到更多好的模式:
发生这种情况的原因是,stateIn操作符根据上游流是否为ChannelFlow进行了一些优化。 .继续。。。返回一个ChannelFlow,而.onEach。。。没有 通常这并不重要。在您的案例中,它之所以重要,是因为您希望stateIn返回的流永远不会发出初始值。但是有一个原因,这个参数是强制性的,您应该期望收到初始值。实际上是否这样做主要取决于上游流是否能够在不挂起的情况下发出值 现在看来,stateIn操作符中的优化之一是,它可能在不挂起的情况下使用ChannelFlow。这就是为什么在使用时会出现预期的行为
.flowOn(testDispatcher) /*returns ChannelFlow*/
.stateIn(GlobalScope, SharingStarted.Lazily, "init state")
你能分享你的Flow.test实现吗?或者如果它是一个依赖项,那么是哪一个。它是按平方涡轮排列的,但是当我使用标准方法收集一些列表中的项目,然后取消热流表时,结果是一样的。这是一个很好的解释。让我澄清一件事。我很惊讶在测试场景中没有得到init状态。你说你是否真的这样做主要取决于上游流是否能够在不暂停的情况下发出一个值。-例如,如果我在makeHeavyRequest中加入延迟,我会得到init状态和heavy结果?好的,我只是检查了一下。就像你说的,我只需要测试Dispatcher.advanceTimeBy。老实说,这种行为对我来说有点奇怪,我来自rxjava世界,正在寻找行为处理器的类似物。还有一件事——我知道ChannelFlow有一些优化。但这最终不是一个bug吗?你认为我应该报告它吗?我不会称之为bug,更多的是一种可以更好地记录的行为。通常在使用stateflow时,您希望以任何方式对最新的值进行操作,因此,如果流已经发出了一个值,那么为什么要保留初始值(可能只是一个占位符)呢?确实,将范围更改为我可以控制的,并将其dispatcher更改为testDispatcher,可以修复测试用例。非常感谢。