KamilH
03/10/2021, 8:44 AMInvalidMutabilityException
when launching new coroutine on Main Dispatcher (code in thread).KamilH
03/10/2021, 8:45 AMclass ViewModel {
var counter: Int = 0
val platform = Platform()
init {
println("init thread: ${platform.currentThread}")
}
fun onClick() {
GlobalScope.launch {
println("GlobalScope thread: ${platform.currentThread}")
withContext(Dispatchers.Main) {
println("withContext thread: ${platform.currentThread}")
counter++
}
}
}
}
KamilH
03/10/2021, 8:47 AMinit thread: <NSThread: 0x600002164a00>{number = 1, name = main}
GlobalScope thread: <NSThread: 0x600002162b00>{number = 8, name = (null)}
withContext thread: <NSThread: 0x600002164a00>{number = 1, name = main}
so it seems like accessing counter
is happening on the same thread as init
has been called, but either way exception gets thrownKamilH
03/10/2021, 8:49 AMonClick
function to the following one:
fun onClick() {
println("onClick thread: ${platform.currentThread}")
counter++
println("counter: $counter")
}
I’m getting similar print:
init thread: <NSThread: 0x600001738240>{number = 1, name = main}
onClick thread: <NSThread: 0x600001738240>{number = 1, name = main}
counter: 1
so I’m in the main thread, but everything is fine and crash is not happeningKamilH
03/10/2021, 8:50 AMlaunch
is making whole ViewModel
class frozen and that is why I’m getting an exception?mbonnin
03/10/2021, 8:52 AMTijl
03/10/2021, 8:52 AMplatform
) from GlobalScope.launch, so your ViewModel object needs to be frozen for this code to work.
I’m not sure but it will probably freeze it anyway using withContext
, also using the correct dispatcherTijl
03/10/2021, 8:52 AMmbonnin
03/10/2021, 8:53 AMwithContext
freezes the block automatically?mbonnin
03/10/2021, 8:55 AMwithContext
without freezing its block if the dispatcher doesn't changeTijl
03/10/2021, 9:02 AMKamilH
03/10/2021, 9:06 AMViewModel
is already frozen and you’re right:
init thread: <NSThread: 0x600001230040>{number = 1, name = main}, isFrozen: false
GlobalScope thread: <NSThread: 0x600001228d80>{number = 8, name = (null)}, isFrozen: true
withContext thread: <NSThread: 0x600001230040>{number = 1, name = main}, isFrozen: true
Tijl
03/10/2021, 9:12 AMval platform = Platform()
to after onClick
the first isFrozen
should go to false, were it not for the fact that you reference it to check if it’s frozen 😃uli
03/10/2021, 9:17 AMGlobalScope.launch(Dispatchers.Main)
or is this withContext
switch integral part of your example?uli
03/10/2021, 9:21 AMfun onClick() {
GlobalScope.launch(Dispatchers.Main) {
withContext(Dispatchers.Default) {
println("Dispatchers.Default thread: ${platform.currentThread}")
}
println("Dispatchers.Main: ${platform.currentThread}")
counter++
}
uli
03/10/2021, 9:24 AMKamilH
03/10/2021, 9:25 AMfun onClick() {
GlobalScope.launch {
val platform = Platform()
println("GlobalScope thread: ${platform.currentThread}, isFrozen: ${platform.isFrozen(this@ViewModel)}")
withContext(Dispatchers.Main) {
println("withContext thread: ${platform.currentThread}, isFrozen: ${platform.isFrozen(this@ViewModel)}")
counter++
}
}
}
I’m still getting:
init thread: <NSThread: 0x6000031f0000>{number = 1, name = main}, isFrozen: false
GlobalScope thread: <NSThread: 0x6000031f2940>{number = 7, name = (null)}, isFrozen: true
withContext thread: <NSThread: 0x6000031f0000>{number = 1, name = main}, isFrozen: true
but I think this time it is because of ${platform.isFrozen(this@ViewModel)
, right?Tijl
03/10/2021, 9:25 AMDispatchers.Default
, so the whole viewmodel will be frozenTijl
03/10/2021, 9:26 AM${platform.isFrozen(this@ViewModel)}
Tijl
03/10/2021, 9:29 AMfun onClick() {
val platform = Platform()
GlobalScope.launch {
println("GlobalScope thread: ${platform.currentThread}")}.join()
println("isFrozen: ${platform.isFrozen(this@ViewModel)}"
// ..
no garantuees, but I think you will get false nowuli
03/10/2021, 9:30 AMplatform.currentThread
is enough to get platform
frozenTijl
03/10/2021, 9:30 AMTijl
03/10/2021, 9:31 AMuli
03/10/2021, 9:31 AMfun onClick() {
GlobalScope.launch(Dispatchers.Main) {
withContext(Dispatchers.Default) {
println("Doing something in the background, not accessing the view model")
}
println("Dispatchers.Main thread: ${platform.currentThread}, isFrozen: ${platform.isFrozen(this@ViewModel)}")
counter++
}
}
Tijl
03/10/2021, 9:32 AMplatform
(and if it’s a field in viewmodel it references viewmodel)uli
03/10/2021, 9:33 AMthis
to reference platform
to reference currentThread
which freezes the whole chain, including this
(aka instance of ViewModel)uli
03/10/2021, 9:36 AMTijl
03/10/2021, 9:44 AMval curThread = currentThread()
val newThread = completion.context[ContinuationInterceptor].thread()
if (newThread != curThread) {
check(start != CoroutineStart.UNDISPATCHED) {
"Cannot start an undispatched coroutine in another thread $newThread from current $curThread"
}
block.freeze()
so indeed when coming from a thread (even if it has no dispatcher) into a dispatcher with the same thread no freeze takes placeTijl
03/10/2021, 9:45 AMKamilH
03/10/2021, 9:59 AMfun onClick() {
GlobalScope.launch(Dispatchers.Main) {
counter++
println(counter)
}
}
I’m not getting exception, while with `withContext`:
fun onClick() {
GlobalScope.launch(Dispatchers.Default) {
withContext(Dispatchers.Main) {
counter++
println(counter)
}
}
}
I’m getting an exception and that is because the thread switching happens here (Default -> Main) and in that time ViewModel
freeze is happening, right?Tijl
03/10/2021, 10:00 AMTijl
03/10/2021, 10:01 AM{
counter++
println(counter)
}
this block itself is created in globalscope, and has to run in main. so the block itself (and everything it references) gets frozenuli
03/10/2021, 10:01 AMTijl
03/10/2021, 10:02 AMblock.freeze()
uli
03/10/2021, 10:03 AMthis
reference get passed to a new thread by launch
just to get passed back by withContext
. This is what causes your freezing. And that’s what is solved by my suggestionKamilH
03/10/2021, 10:04 AMAny
to see when it’s getting frozen 🙂uli
03/10/2021, 10:04 AMKamilH
03/10/2021, 10:05 AMfreezing
stuff 🙂uli
03/10/2021, 10:06 AMval vmScope = CoroutineScope(Job() + Dispatchers.Main)
fun onClick() {
vmScope.launch {
withContext(Dispatchers.Default) {
println("Doing something in the background, not accessing the view model")
}
println("vmScope thread: ${platform.currentThread}, isFrozen: ${platform.isFrozen(this@ViewModel)}")
counter++
}
}
Tijl
03/10/2021, 10:07 AMfun onClick() {
MainScope().launch {
// we came from main thread, nothing is frozen
withContext(Dispatchers.Default) {
// do background work but make sure not to needlessly capture stuff from the viewmodel
}
}
}
Tijl
03/10/2021, 10:10 AMuli
03/10/2021, 10:11 AMTijl
03/10/2021, 10:11 AMMainScope()
, use a scope tied to the lifecycle of your viewmodel with the Dispatchers.Main(.immidiate)Tijl
03/10/2021, 10:11 AMTijl
03/10/2021, 10:13 AMKamilH
03/10/2021, 10:26 AMclass ViewModel {
private val scope = CoroutineScope(Dispatchers.Main + SupervisorJob())
val state = MutableStateFlow("")
fun loadData() {
scope.launch {
state.value = longRunningJob()
}
}
private suspend fun longRunningJob(): String =
withContext(Dispatchers.Default) {
Thread.sleep(5000)
""
}
}
in Android world is a bad practise because I’m capturing ViewModel's
variable (state
)? Or because of the fact that scope
is running in Main
and only longRunningJob
makes Dispatcher switching it’s safe?KamilH
03/10/2021, 10:29 AMDefault
and I’m calling them from Main
. I always thought it’s safe 😅uli
03/10/2021, 10:31 AMdelay
you could stay on the main threadKamilH
03/10/2021, 10:34 AM