MutableLiveData:无法在协程的后台线程上调用 setValue

MutableLiveData: Cannot invoke setValue on a background thread from Coroutine

我正在尝试从协程触发 LiveData 更新:

object AddressList: MutableLiveData<List<Address>>()
fun getAddressesLiveData(): LiveData<List<Address>> {
    AddressList.value = listOf()
    GlobalScope.launch {
        AddressList.value = getAddressList()
    }
    return AddressList
}

但我收到以下错误:

IllegalStateException: Cannot invoke setValue on a background thread

有没有办法让它与协程一起工作?

我刚刚发现可以使用 withContext(Dispatchers.Main){}:

object AddressList: MutableLiveData<List<Address>>()
fun getAddressesLiveData(): LiveData<List<Address>> {
    GlobalScope.launch {
        withContext(Dispatchers.Main){ AddressList.value = getAddressList() }
    }
    return AddressList
}

您可以执行以下操作之一:

object AddressList: MutableLiveData<List<Address>>()
fun getAddressesLiveData(): LiveData<List<Address>> {
    AddressList.value = listOf()
    GlobalScope.launch {
        AddressList.postValue(getAddressList())
    }

return AddressList
}

fun getAddressesLiveData(): LiveData<List<Address>> {
    AddressList.value = listOf()
    GlobalScope.launch {
        val adresses = getAddressList()
        withContext(Dispatchers.Main) {
            AddressList.value = adresses
        }
    }
    return AddressList
}

虽然其他人指出,在这种情况下,库提供了自己的方法来 post 对主线程的操作,但协程提供了一个通用的解决方案,无论给定库的功能如何。

第一步是停止对后台作业使用 GlobalScope,这样做会导致泄漏,您的 activity、预定作业或您从中调用它的任何工作单元可能会泄漏被销毁,但你的工作将在后台继续,甚至将其结果提交到主线程。 official documentation on GlobalScope 的内容如下:

Application code usually should use application-defined CoroutineScope, using async or launch on the instance of GlobalScope is highly discouraged.

您应该定义自己的协程作用域,它的 coroutineContext 属性 应该包含 Dispatchers.Main 作为调度程序。此外,在函数调用和 returning LiveData(基本上是另一种 Future)中启动作业的整个模式并不是使用协程的最方便方式。相反,你应该

suspend fun getAddresses() = withContext(Dispatchers.Default) { getAddressList() }

并且在调用站点你应该 launch 一个协同程序,你现在可以在其中自由调用 getAddresses() 就好像它是一个阻塞方法一样并直接获取地址作为 return值。

在我的例子中,我必须将 Dispatchers.Main 添加到启动参数中,并且效果很好:

 val job = GlobalScope.launch(Dispatchers.Main) {
                    delay(1500)
                    search(query)
                }

如果您想使用协程更新UI,有两种方法可以实现

GlobalScope.launch(Dispatchers.Main):

GlobalScope.launch(Dispatchers.Main) {
    delay(1000)     // 1 sec delay
    // call to UI thread
}

如果您想在后台完成一些工作,但之后又想更新UI,这可以通过以下方式实现:

withContext(Dispatchers.Main)

GlobalScope.launch {
    delay(1000)     // 1 sec delay

    // do some background task

    withContext(Dispatchers.Main) {
            // call to UI thread
    }
}

使用 liveData.postValue(value) 而不是 liveData.value = value。它称为异步。

来自documentation

postValue - Posts a task to a main thread to set the given value.

我在为测试用例调用 runBlockingTest 中的协程时遇到了这个错误

TestCoroutineRule().runBlockingTest {

}

我通过将 instantExecutorRule 添加为 class 成员来修复它

@get:Rule
var instantExecutorRule = InstantTaskExecutorRule()

以下代码适用于我从线程更新实时数据:

 val adresses = getAddressList()
 GlobalScope.launch {
     messages.postValue(adresses)
 }