Kotlin / Multithreading natif à l'aide de coroutines

Aug 19 2020

J'ai eu une chance sur la multiplateforme kotlin et c'est génial, mais le filetage me laisse perplexe. Le gel de l'état entre les threads a du sens sur le plan conceptuel et fonctionne très bien dans des exemples simples où de petits objets ou primitives sont passés dans les deux sens, mais dans les applications du monde réel, je ne peux pas contourner InvalidMutabilityException.

Prenez l'extrait de code commun suivant à partir d'une application 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
            }
    }
}

Rien de très complexe, mais s'il est utilisé dans du code commun et exécuté à partir de Kotlin / Native, pow InvalidMutabilityException sur MainViewModel.

Il semble que la raison en est que tout ce qui est passé dans withContext est gelé de manière récursive, car objectWhichContainsNetworking est une propriété de MainViewModel et est utilisé dans withContext, alors MainViewModel est pris dans le gel.

Ma question est donc la suivante: est-ce juste une limitation du modèle de mémoire Kotlin / Native actuel? Ou peut-être la version actuelle des coroutines? Et y a-t-il des moyens de contourner cela?

Remarque: version coroutines: 1.3.9-native-mt. kotlin version 1.4.0.


Edit 1: Il semble donc que le code allégé ci-dessus fonctionne correctement. Il s'avère que le code incriminant était une variable modifiable dans le modèle de vue (utilisée pour conserver une référence au dernier état de vue) qui devient gelée puis lève l'exception lorsqu'elle tente d'être mutée. Je vais essayer d'utiliser Flow / Channels pour m'assurer qu'il n'y a pas de référence var nécessaire et voir si cela résout le problème global.

Remarque: s'il existe un moyen d'éviter que MainViewModel ne soit gelé en premier lieu, ce serait toujours fantastique!


Edit 2: Remplacement du var par Flow. Je ne pouvais pas obtenir la collecte de flux standard dans iOS avant d'utiliser les helpers ici:https://github.com/JetBrains/kotlinconf-app/blob/master/common/src/mobileMain/kotlin/org/jetbrains/kotlinconf/FlowUtils.kt.

MainViewModel est toujours gelé, mais comme tout son état est immuable, ce n'est plus un problème. J'espère que cela aide quelqu'un!

Réponses

4 KevinGalligan Aug 19 2020 at 21:14

Dans votre code d'origine, vous faites référence à un champ de l'objet parent, ce qui vous oblige à capturer le parent entier et à le figer. Ce n'est pas un problème avec les coroutines. Coroutines suit les mêmes règles que toutes les autres bibliothèques de concurrence dans Kotlin / Native. Il gèle le lambda lorsque vous croisez des threads.

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) {}
    }
}

Pour éviter que cela ne se produise:

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

La chose la plus compliquée avec le modèle de mémoire est la suivante. S'habituer à ce qui est capturé et l'éviter. Ce n'est pas si difficile quand on s'y habitue, mais il faut apprendre les bases.

J'en ai longuement parlé:

Pratique Kotlin / Concurrence native

Kotlin Native Concurrency Hands On

Concurrence KN KotlinConf

Le modèle de mémoire est en train de changer, mais cela prendra un certain temps avant que cela n'arrive. Une fois que vous vous êtes habitué au modèle de mémoire, les problèmes immuables sont généralement simples à diagnostiquer.