Jokubas Trinkunas
Jokubas Trinkunas

Reputation: 854

Thread safe LiveData updates

I have the following code which has a race condition. I try to find an item in a list and set its loading property. But if onLoaded("A") and onLoaded("B") are called multiple times from different threads. I always lose the data of the first call if it doesn't complete before second starts.

How can I make this work? Is using Mutex should be the correct approach?

val list = MutableLiveData<List<Model>>() // assume this is initialized with ["Model(false, "A"), Model(false, "B")]

data class Model(
    val loaded: Boolean,
    val item: String,
)

fun onLoaded(item: String) = viewModelScope.launch {
    val currList = list.value ?: return@launch

    withContext(Dispatchers.Default) {
        val updated = currList.find { it.item == item }?.copy(loaded = true)
        val mutable = currList.toMutableList()
        updated?.let {
            val index = mutable.indexOf(it)
            mutable[index] = it
        }
        list.postValue(mutable.toList())
    }
}

onLoaded("A")
onLoaded("B")

expected: ["Model(true, "A"), Model(true, "B")]
actual: ["Model(false, "A"), Model(true, "B")]

Upvotes: 0

Views: 1456

Answers (1)

Sergio
Sergio

Reputation: 30605

In onLoaded() a new coroutine is launched using viewModelScope. viewModelScope has Dispatchers.Main.immediate context, so the code inside it will be executed on the Main Thread, e.g. execution is limited to only one thread. The reason you have a Race Condition because calling the onLoaded() function consecutively doesn't guarantee the order of coroutines execution.

  • If you call onLoaded() consecutively from one thread I suggest to remove launching a coroutine viewModelScope.launch in it. Then the order of calling will be preserved. Use list.postValue() in this case.

  • If you call onLoaded() from different threads and still want to launch a coroutine you can refer to answers to this question.

  • Try to use @Synchronized anotation without launching a coroutine:

    @Synchronized
    fun onLoaded(item: String) { ... }
    

    Method will be protected from concurrent execution by multiple threads by the monitor of the instance on which the method is defined. Use list.postValue() in this case.

Upvotes: 1

Related Questions