在viewModelScope中从Flow收集数据可能会阻止Android Studio中的UI吗



代码A来自关于Flow 的官方文章

viewModelScope.launch{}默认在UI线程中运行,我认为suspend fun fetchLatestNews()默认也会在UI线程上运行,所以我认为当fetchLatestNews()是长时间操作时,代码A可能会导致UI阻塞,对吧?

我认为代码B可以解决问题,对吧?

代码A

class LatestNewsViewModel(
private val newsRepository: NewsRepository
) : ViewModel() {
init {
viewModelScope.launch {
// Trigger the flow and consume its elements using collect
newsRepository.favoriteLatestNews.collect { favoriteNews ->
// Update View with the latest favorite news
}
}
}
}

class NewsRemoteDataSource(
private val newsApi: NewsApi,
private val refreshIntervalMs: Long = 5000
) {
val latestNews: Flow<List<ArticleHeadline>> = flow {
while(true) {
val latestNews = newsApi.fetchLatestNews()
emit(latestNews) // Emits the result of the request to the flow
delay(refreshIntervalMs) // Suspends the coroutine for some time
}
}
}
// Interface that provides a way to make network requests with suspend functions
interface NewsApi {
suspend fun fetchLatestNews(): List<ArticleHeadline>
}

代码B

class LatestNewsViewModel(
private val newsRepository: NewsRepository
) : ViewModel() {
init {
viewModelScope.launch(Dispatchers.IO) {
//The same
}
}
}

//The same

添加内容:

致Tenfour04:谢谢!

我认为挂起函数可能会阻塞UI,所以我认为定义dispatchers是最重要的,这样才不会阻塞UI!对吗?

当我点击";启动";按钮显示信息,如果我使用Dispatchers.IO

当我点击"0"时,代码2被冻结并且没有信息被更新;启动";按钮,如果我使用Dispatchers.Main

如果我使用Dispatchers.Main,代码3可以像代码1一样工作,原因只是因为我设置了delay(100)

BTW,流量暂停。因此,如果有一个长时间的操作,即使它是用协程而不是soundDbFlow().collect { myInfo.value = it.toString() }包装的,我想我可以得到与代码1、代码2和代码3相同的测试结果。

更重要的是,在我为流添加了flowOn(Dispatchers.IO)之后,代码4是可以的,即使它是在viewModelScope.launch(Dispatchers.Main){}中启动的!

代码1正常

class HandleMeter: ViewModel() {
var myInfo = mutableStateOf("World")
private var myJob: Job?=null
private var k=0
private fun soundDbFlow() = flow {
while (true) {
emit(k++)
delay(0)
}
}
fun calCurrentAsynNew() {
myJob?.cancel()
myJob = viewModelScope.launch(Dispatchers.IO){
soundDbFlow().collect { myInfo.value = it.toString() }
}
}
fun cancelJob(){
myJob?.cancel()
}
}

@Composable
fun Greeting(handleMeter: HandleMeter) {
var info = handleMeter.myInfo
Column(
modifier = Modifier.fillMaxSize()
) {
Text(text = "Hello ${info.value}")
Button(
onClick = { handleMeter.calCurrentAsynNew() }
) {
Text("Start")
}
Button(
onClick = { handleMeter.cancelJob() }
) {
Text("Stop")
}
}
}

代码2已释放

class HandleMeter: ViewModel() {
private fun soundDbFlow() = flow {
while (true) {
emit(k++)
delay(0)
}
}
fun calCurrentAsynNew() {
myJob?.cancel()
myJob = viewModelScope.launch(Dispatchers.Main){
soundDbFlow().collect { myInfo.value = it.toString() }
}
}
...
//The same
}
...
//The same

代码3正常

class HandleMeter: ViewModel() {
private fun soundDbFlow() = flow {
while (true) {
emit(k++)
delay(100)  //It's 100
}
}
fun calCurrentAsynNew() {
myJob?.cancel()
myJob = viewModelScope.launch(Dispatchers.Main){
soundDbFlow().collect { myInfo.value = it.toString() }
}
}
...
//The same
}
...
//The same

代码4 Ok

class HandleMeter: ViewModel() {
private fun soundDbFlow() = flow {
while (true) {
emit(k++)
delay(0)
}
}.flowOn(Dispatchers.IO)// I added
fun calCurrentAsynNew() {
myJob?.cancel()
myJob = viewModelScope.launch(Dispatchers.Main){
soundDbFlow().collect { myInfo.value = it.toString() }
}
}
...
//The same
}
...
//The same

挂起函数不会阻塞,除非它打破了挂起函数永远不能阻塞的约定。因此,从主调度程序调用协同程序并不重要。调用fetchLatestNews()不会阻塞主线程,除非您对函数的实现进行了不正确的组合,使其实际阻塞。

您通常不需要像代码B:那样执行此操作

viewModelScope.launch(Dispatchers.IO) {

因为您通常不会在协程的顶级调用阻塞函数。如果你是,你可以用withContext(Dispatchers.IO) { }包裹这些碎片。通常将协程留在主调度器上更方便,因为Android中有太多非挂起函数需要从主线程调用它们。如果你把它翻转过来,你可能会在更多的地方需要withContext(Dispatchers.Main) { },而不是相反的地方,而且在协同程序真正开始之前,你也会产生一帧延迟。此外,如果您的协同程序与ViewModel中的属性交互,那么如果您只从主调度程序接触属性,则可以避免并发访问属性的潜在问题,因为它是单线程的。

如果您启动的协程不与任何Main所需的函数交互,并且直接调用阻塞函数,可能会有例外,但我认为这种情况应该很少见,尤其是如果您实践了良好的封装(请参阅此处(。如果您将协程顶层的一块代码分解为自己的函数,则可以将该单独的函数制作为一个挂起函数,如果需要,可以使用withContext(Dispatchers.IO)。然后你的顶级出游会看起来非常干净。

代码A不会阻塞UI线程,因为launch方法不会阻塞当前线程。

正如文件所说:

在不阻塞当前线程的情况下启动一个新的协程,并以[Job]的形式返回对该协程的引用。

如果上下文没有任何调度器或任何其他[ContinutionInterceptor],则使用[Dispatchers.Default]。

因此,在您的情况下,CodeA在引擎盖下使用Dispatches.Default,而CodeB使用Dispatchers.IO

@qki写对了。但他的回答不准确。ViewModel具有viewModelScope。viewModelScope具有上下文SuperVisorJob((+Dispatchers.Main.immedially。LatestNewsViewModel的init将在主组线程中执行,但这不会阻止ui线程。

相关内容

最新更新