1

I have two suspend methods that I launch parallely inside a ViewModel:

init {
    viewModelScope.launch(Dispatchers.Default) {
        launch { loadTotalCirculation() }
        launch { loadMarketPrice() }
    }
}

private suspend fun loadTotalCirculation() {
    val totalCirculation = bitcoinChartsRepository.getTotalCirculation(5, TimeUnit.HOURS)
    _viewState.value = _viewState.value.copy(totalCirculation = chartViewEntityMapper(totalCirculation))
}

private suspend fun loadMarketPrice() {
    val marketPrice = bitcoinChartsRepository.getMarketPrice(27, TimeUnit.DAYS)
    _viewState.value = _viewState.value.copy(marketPrice = chartViewEntityMapper(marketPrice))
}

However, I would like to prevent the concurrent execution of the _viewState.value = _viewState.value.copy... parts in both my methods. What's the best way to achieve this ?

General Grievance
  • 4,555
  • 31
  • 31
  • 45
Shivam Verma
  • 7,973
  • 3
  • 26
  • 34
  • You can use a [`Mutex`](https://kotlinlang.org/docs/shared-mutable-state-and-concurrency.html#mutual-exclusion) but concurrency shouldn't be an issue when you're using `viewModelScope` as it's backed by `Dispatchers.Main.immediate` so both value modifications will happen on main UI thread either way. – Pawel Jun 03 '21 at 19:46
  • Hi Pawel, that's a great point! I am however launching the coroutines on the default dispatcher, so they can run concurrently. I have now updated the code above. – Shivam Verma Jun 03 '21 at 19:59

1 Answers1

4

There are several possibilities to synchronize parallel coroutines. Probably the easiest is to create a single thread context, similar to main thread or to use Mutex. Note this mutex is designed specifically for couroutines, it is not something from Java stdlib.

Single thread context:

val context = Executors.newSingleThreadExecutor().asCoroutineDispatcher()

private suspend fun loadTotalCirculation() {
    val totalCirculation = bitcoinChartsRepository.getTotalCirculation(5, TimeUnit.HOURS)
    withContext (context) {
        _viewState.value = _viewState.value.copy(totalCirculation = chartViewEntityMapper(totalCirculation))
    }
}

private suspend fun loadMarketPrice() {
    val marketPrice = bitcoinChartsRepository.getMarketPrice(27, TimeUnit.DAYS)
    withContext (context) {
        _viewState.value = _viewState.value.copy(marketPrice = chartViewEntityMapper(marketPrice))
    }
}

Alternatively, instead of creating your own thread, you can reuse the main thread by: withContext(Dispatchers.Main).

Mutex:

val mutex = Mutex()

private suspend fun loadTotalCirculation() {
    val totalCirculation = bitcoinChartsRepository.getTotalCirculation(5, TimeUnit.HOURS)
    mutex.withLock {
        _viewState.value = _viewState.value.copy(totalCirculation = chartViewEntityMapper(totalCirculation))
    }
}

private suspend fun loadMarketPrice() {
    val marketPrice = bitcoinChartsRepository.getMarketPrice(27, TimeUnit.DAYS)
    mutex.withLock {
        _viewState.value = _viewState.value.copy(marketPrice = chartViewEntityMapper(marketPrice))
    }
}

Using a main thread or mutex is probably preferred, because if we create our own thread, we need to make sure to properly stop/close it when we won't need it anymore.

You can read more in this article from the official docs: https://kotlinlang.org/docs/shared-mutable-state-and-concurrency.html

broot
  • 21,588
  • 3
  • 30
  • 35
  • thanks! I've used the mutex solution for now but had also tried the other approach you suggested by using the Main dispatcher context. Gonna look at the pros and cons of both the approaches. – Shivam Verma Jun 04 '21 at 10:27
  • 1
    This is actually a very good question: what are pros and cons. I don't feel confident enough to answer it fully, but my thoughts are: I usually avoid mutexes, because they produce more complicated and less readable code. A queue of tasks feels more natural to me, so: actor model, event-loop or single-threaded executor - they all works in a similar fashion. In this specific case I would say mutex is the way to go. Mutual exclusion is exactly what it was designed for. And it doesn't depend on any external machinery nor has any side effects. Other solutions require some external support. – broot Jun 04 '21 at 11:09