未指定调度器时viewModelScope.launch为何不阻塞主线程?
协程执行逻辑与LiveData赋值疑问解答
问题代码与现象
用户在ViewModel中编写了如下代码:
fun doSomethingInViewModelScope(){ viewModelScope.launch { nonBlockingOne() count() nonBlockingTwo() } } fun nonBlockingOne(){ Log.d("ViewModel","Should Be First") } fun nonBlockingTwo(){ Log.d("ViewModel","Should be third") } suspend fun count(){ var shouldCount = false var index = 0 while (!shouldCount){ delay(1000) Log.d("ViewModel","Counter: $index") index +=1 shouldCount = index == 5 } Log.d("ViewModel","Should be second") }
预期与实际差异
- 预期:日志顺序乱序(比如
nonBlockingTwo先执行)或主线程阻塞导致UI冻结 - 实际:日志严格顺序执行,且UI可正常交互:
Should be First Counter: 0 Counter: 1 Counter: 2 Counter: 3 Counter: 4 Should be second Should be third
同时用户发现,在该协程中执行liveDdata.value = 10并未抛出"仅可在主线程执行"的异常,对此感到困惑。
问题解答
1. 为什么协程顺序执行且UI不冻结?
- 顺序执行原因:
viewModelScope.launch块内的代码默认是串行执行的,除非显式使用async、嵌套launch或切换调度器做并发处理。所以nonBlockingOne执行完毕后才会进入suspend fun count(),等count完全执行结束,才会调用nonBlockingTwo,最终形成严格的顺序日志。 - UI不冻结原因:
viewModelScope默认使用Dispatchers.Main调度器,但delay是非阻塞式挂起函数。当协程执行到delay(1000)时,会主动挂起并让出主线程,此时主线程可继续处理UI事件;延迟时间到后,协程会在主线程恢复执行。整个过程没有阻塞主线程,因此UI不会冻结。
2. 为什么LiveData.value赋值未抛出异常?
LiveData要求value的赋值操作必须在主线程执行,而viewModelScope.launch默认就是在主线程运行的协程,直接调用liveDdata.value = 10完全符合LiveData的线程要求,自然不会抛出异常。只有在子线程中直接给value赋值时,才会触发"仅可在主线程执行"的异常,此时需要用postValue切换到主线程更新。
内容的提问来源于stack exchange,提问作者Andreas1234
相关产品推荐
相关产品推荐

