Kotlin / Multithreading nativo utilizzando coroutines

Aug 19 2020

Sto provando con kotlin multipiattaforma ed è geniale, ma il threading mi dà fastidio. Il congelamento dello stato tra i thread ha senso concettualmente e funziona bene in semplici esempi in cui piccoli oggetti o primitive vengono passati avanti e indietro, ma nelle applicazioni del mondo reale non riesco a aggirare InvalidMutabilityException.

Prendi il seguente frammento di codice comune da un'app Android

class MainViewModel(
    private val objectWhichContainsNetworking: ObjectWhichContainsNetworking
)

    private var coreroutineSupervisor = SupervisorJob()
    private var coroutineScope: CoroutineScope = CoroutineScope(Dispatchers.Main + coreroutineSupervisor)

    private fun loadResults() {
        // Here: Show loading
        coroutineScope.launch {
            try {
                val result = withContext(Dispatchers.Default) { objectWhichContainsNetworking.fetchData() }
                // Here: Hide loading and show results
            } catch (e: Exception) {
                // Here: Hide loading and show error
            }
    }
}

Niente di molto complesso, ma se usato nel codice comune ed eseguito da Kotlin / Native, allora pow InvalidMutabilityException su MainViewModel.

Sembra che la ragione di ciò sia che tutto ciò che viene passato withContext viene congelato in modo ricorsivo, quindi perché objectWhichContainsNetworking è una proprietà di MainViewModel ed è utilizzato in withContext, MainViewModel viene bloccato nel blocco.

Quindi la mia domanda è: questa è solo una limitazione dell'attuale modello di memoria Kotlin / Native? O forse la versione attuale delle coroutine? E ci sono modi per aggirare questo?

Nota: versione coroutines: 1.3.9-native-mt. kotlin versione 1.4.0.


Modifica 1: quindi sembra che il codice sopra ridotto funzioni effettivamente bene. Si scopre che il codice incriminante era una variabile aggiornabile nel modello di visualizzazione (utilizzata per mantenere un riferimento all'ultimo stato di visualizzazione) che viene congelata e quindi genera l'eccezione quando tenta di essere modificata. Farò un tentativo di utilizzare Flow / Channels per assicurarmi che non sia necessario alcun riferimento var e vedere se questo risolve il problema generale.

Nota: se c'è un modo per evitare che MainViewModel venga bloccato in primo luogo, sarebbe comunque fantastico!


Modifica 2: sostituito il var con Flow. Non sono riuscito a raccogliere il flusso standard in iOS finché non ho utilizzato gli helper qui:https://github.com/JetBrains/kotlinconf-app/blob/master/common/src/mobileMain/kotlin/org/jetbrains/kotlinconf/FlowUtils.kt.

MainViewModel viene ancora bloccato, ma poiché tutto il suo stato è immutabile, non è più un problema. Spero che aiuti qualcuno!

Risposte

4 KevinGalligan Aug 19 2020 at 21:14

Nel codice originale, fai riferimento a un campo dell'oggetto genitore, il che ti fa catturare l'intero genitore e congelarlo. Non è un problema con le coroutine. Coroutines segue le stesse regole di tutte le altre librerie di concorrenza in Kotlin / Native. Blocca il lambda quando si incrociano i thread.

class MainViewModel(
    private val objectWhichContainsNetworking: ObjectWhichContainsNetworking
)

//yada yada
    private fun loadResults() {

        coroutineScope.launch {
            try {

                val result = withContext(Dispatchers.Default) {

                    //The reference to objectWhichContainsNetworking is a field ref and captures the whole view model
                    objectWhichContainsNetworking.fetchData() 

            }

        } catch (e: Exception) {}
    }
}

Per evitare che ciò accada:

class MainViewModel(
    private val objectWhichContainsNetworking: ObjectWhichContainsNetworking
){
    init{
        ensureNeverFrozen()
    }
    //Etc

La cosa più complicata con il modello di memoria è questa. Abituarsi a ciò che viene catturato ed evitarlo. Non è così difficile quando ti ci abitui, ma devi imparare le basi.

Ne ho parlato a lungo:

Kotlin pratico / concorrenza nativa

Kotlin Native Concurrency Hands On

KotlinConf KN Concurrency

Il modello della memoria sta cambiando, ma ci vorrà un bel po 'prima che arrivi. Una volta che ci si abitua al modello di memoria, i problemi immutabili sono generalmente semplici da diagnosticare.