ursus
05/16/2021, 3:09 PMclass Syncer(private val somePartialSyncComponentNeverRelatedToUi) {
private val scope = CoroutineScope(<http://Dispatcher.IO|Dispatcher.IO>)
fun sync() {
scope.launch {
somePartialSyncComponent.doWhatever()
}
}
}
class SomePartialSyncComponentNeverRelatedToUi {
suspend func doWhatever {
withContext here ??? <--------
}
}
Is it not wasteful context switch?Adam Powell
05/16/2021, 6:26 PMErik
05/16/2021, 6:26 PMursus
05/16/2021, 9:49 PMclass SendRepository {
override suspend fun sendData(tuid: String, sim: Sim) {
return withContext(<http://dispatcherProvider.io|dispatcherProvider.io>) {
try {
val apiSim = SimDataRequestMapper(sim)
apiClient.sendSimData(tuid, apiSim)
} catch (ex: Exception) {
throw when {
ex is ApiException && ex.errorCode == 400 && ex.errorMessage.equals(
"SIM_CARD_ALREADY_USED",
true
) -> SimCardAlreadyActivatedException()
ex is ApiException && ex.errorCode == 400 &&
(ex.errorMessage.equals(
"SIM_ICC_PUK_MISMATCH",
true
) || ex.errorMessage.equals(
"UNKNOWN_SIM",
true
)) -> SimDataMismatchException()
else -> ex
}
}
}
}
}
class ViewModel {
fun foo() {
scope.launch {
try {
_state.value = SENDING
sendRepositroy.sendData(..)
_state.value = SENT
} catch() {
_state.value = ERROR
}
}
}
}
Erik
05/17/2021, 7:33 AMmyungpyo.shim
05/17/2021, 7:58 AMwithContext(<http://Dispatcher.XXX|Dispatcher.XXX>)
syntax.
In short, ViewModels don't need to know what dispatchers they need to use. They just call some suspending function in a desired coroutine scope (in this case, It's ViewModelScope).
In addition, If some of DataSources need to change there context( := dispatcher), They freely use withContext
syntax.
(In my production code, all of the encryption/decryption logic must be executed in the dedicated single thread. So I create that dispatcher and the datasource which should be encrypted use this dispatcher.)
---
Rule of thumb is "Callers don't need to care about the dispatchers that callee is going to use."
(Below picture is my clumsy artwork 😅)ursus
05/17/2021, 12:29 PMErik
05/17/2021, 1:23 PMDispatchers.Default
), that seems unnecessaryursus
05/17/2021, 1:34 PMAdam Powell
05/17/2021, 1:57 PMursus
05/17/2021, 3:22 PMAdam Powell
05/17/2021, 3:30 PMursus
05/17/2021, 3:31 PMAdam Powell
05/17/2021, 3:31 PMursus
05/17/2021, 3:42 PMAdam Powell
05/17/2021, 4:25 PMsuspend
or not. If it's suspend
, a caller should be able to assume it doesn't block a calling thread.ursus
05/17/2021, 5:05 PMAdam Powell
05/17/2021, 5:18 PMursus
05/17/2021, 5:30 PMAdam Powell
05/17/2021, 6:05 PMursus
05/17/2021, 6:14 PMAdam Powell
05/17/2021, 6:21 PMmyDataClass.copy(someProperty = someNewValue)
into a withContext(Dispatchers.Default)
even though it is indeed performing blocking computation that will return whenever it returns. From there we're just quibbling over the shades of gray.ursus
05/17/2021, 6:25 PMmyungpyo.shim
05/18/2021, 1:07 AMwithContext(<http://Dispatchers.IO|Dispatchers.IO>)
in the most of UseCase class because it is the boundary between UI and DataLayer (Most of usecases are executing I/O bound tasks).
Of course, we can just run those kind of tasks without another Dispatcher (Execute directly in the main thread thanks to its suspending nature).
But I don't want to put I/O bound tasks into the Ui related Main Queue which will be parked until it has signal from disk or socket.
(BTW, AFAIK disk i/o is incapable of triggering data available signal unlike socket i/o - so it need polling internally.)
If I have many pending i/o bound tasks(coroutines) in Ui Thread dispatching queue, There will be high chance that I can see jank in UI. Moreover, As using I/O dispatcher those coroutines can be scheduled in parallel.
I'm not sure this is sufficient reason to use background dispatchers (I/O, Default, and so on...) for you.ursus
05/18/2021, 10:57 AMmyungpyo.shim
05/18/2021, 1:58 PMwithContext(<http://Dispatchers.IO|Dispatchers.IO>)
in all of my usecase due to the fact that most of my usecases execute I/O bound tasks(disk, network).
---
One more trivial thing that I want to share. See below.
ViewModel (care about coroutine scope)
-> UseCase (withContext(IO)
)
-> Repository (Accidently declare withContext(IO)
)
-> DataSource (withContext(SingleThreaded)
)
-> Api (Retrofit) or Database (Room)
In this case, UseCase is kind of boundary between UI and Data layer. and A repository accidently use the same dispatcher with the usecase. AFAIK, there won't be context switching because coroutine scheduler is aware of the type of dispatcher.
---
I declare data layer boundary and all of the low layers under the boundary just do their job believing they are on the I/O scheduler. and if some of them want to use another scheduler, they just freely specify withContext(BlahBlah).
(Google I/O is starting soon! Enjoy! 🙂 )