我对 Kotlin 中的 LauchedEffect 和协程或线程感到困惑。它们是在另一个之上运行的吗?

问题描述 投票:0回答:1

在可组合函数中,每次更改状态变量时,该函数都会从上到下再次运行,并且所有 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() 函数的结果还是会尝试运行另一个实例?

multithreading async-await kotlin-coroutines launch effect
1个回答
0
投票

正如 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。
    

© www.soinside.com 2019 - 2024. All rights reserved.