在可组合函数中,每次更改状态变量时,该函数都会从上到下再次运行,并且所有 LaunchedEffect(Unit) 都会再次运行。
当我在 LaunchedEffect 中有一个协程时,该例程将再次运行。但是当例程作为线程运行时会发生什么?
假设我有:
var res = 0
LaunchedEffect(Unit) {
val job = scope.async(Dispatcher.IO) {
res = getsomething() //Long process
}
job.await()
}
getsomething() 函数是否会再次运行并生成多个线程,或者如果它仍在运行第一个线程,则会被忽略?
假设我有:
var res = 0
LaunchedEffect(Unit) {
val job = scope.launch(Dispatcher.IO) {
res = getsomething() //Long process
}
job.join()
}
我猜与异步相同,但它是相同的响应吗?
假设我有:
var res = 0
LaunchedEffect(Unit) {
res = suspend_get_other_something()
}
在其他可组合函数中,类似这样:
suspend fun suspend_get_other_something(): Int {
LaunchedEffect(Unit) {
val res = scope.launch {
MyViewModel.runStuff()
//Get Some Data from Internet. Internally it wont get more data until it is
//finished and emit the outcome received in the state and error variables bellow
}
res.join()
}
val state by MyViewModel.state.collectAsStateWithLifecycle()
val error by MyViewModel.error.collectAsStateWithLifecycle()
LaunchedEffect(state) {
if (state == -1) return@LaunchedEffect
else {
MyViewModel.resetState()
return state
}
}
}
LaunchedEffect(error) {
if (error == -1) return@LaunchedEffect
else {
MyViewModel.resetError()
return error
}
}
在挂起函数 [suspend_get_other_something()] 的情况下会发生什么,它会运行同一个实例并获取 runStuff() 函数的结果还是会尝试运行另一个实例?
正如 broot 在评论中正确指出的那样,当创建 new LaunchedEffect 时,LaunchedEffect 启动的协程将在重组时被取消。但事实并非如此,因为新的 LaunchedEffect 仅在其键发生更改时才会启动。由于您使用
Unit
作为永远不会改变的键,因此 LaunchedEffect 将不会在重组时重新启动。因此它只会被执行一次。仅当可组合项完全离开合成并稍后重新进入时(例如,在设备重新配置(例如屏幕旋转)时),LaunchedEffect 才会被取消并重新启动。
但是这里还有另一个问题:在 LaunchedEffect 启动的协程中,您启动了 another 协程(由
async
和 launch
),并且当 LaunchedEffect 的协程被取消时,该协程不会被取消。它取决于 scope
,因此仅当 that被取消时才会被取消。 这似乎不对,所以你应该用这个替换前两个 LaunchedEffects:
LaunchedEffect(Unit) {
withContext(Dispatchers.IO) {
res = getsomething()
}
}
也就是说,在可组合项中切换到 IO 调度程序有点可疑。您的可组合项应该只关注与 UI 直接相关的任何内容。正确的调度程序是
Dispatchers.Main
, LaunchedEffect 已经在其上运行。如果
getsomething()
确实应该在 IO 调度程序上运行,那么您的可组合项可能根本就不应该调用它。这就是视图模型发挥作用的地方:它比 UI 更长寿,并且可以处理与 IO 相关的所有内容,例如文件系统、网络或数据库访问。这还有一个额外的好处,即视图模型与可组合项的重组分离。因此,只需在那里启动一个新的协程(在viewModelScope
中)并调用
getsomething()
。如果它是一次性操作,则可以在 init
块中执行此操作,或者将其绑定到用户操作,例如单击按钮调用视图模型函数,该函数进而启动 getsomething()
的新协程。只要确保永远不要在视图模型中公开挂起函数即可。这样您就不需要 LaunchedEffect 来首先访问您的视图模型。恐怕我不明白你的最后一个例子,因为它无法编译,但我想我上面解释的内容也应该对这里有所帮助。只需确保处理视图模型中的所有 IO,并在 StateFlow 中公开和状态更改。 控制 IO 可以由 UI 通过调用适当的视图模型函数(runStuff
、
resetState
、resetError
)来启动,只需确保它们不会挂起并且没有返回值。他们的唯一目的是实际做肮脏的工作并根据需要更新您的 StateFlow。