Kotlin / Родная многопоточность с использованием сопрограмм

Aug 19 2020

У меня была возможность попробовать мультиплатформенность kotlin, и это великолепно, но многопоточность меня ставит в тупик. Замораживание состояния между потоками имеет смысл концептуально и отлично работает в простых примерах, где небольшие объекты или примитивы передаются туда и обратно, но в реальных приложениях я не могу обойти InvalidMutabilityException.

Возьмите следующий общий фрагмент кода из приложения для 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
            }
    }
}

Ничего особенного, но если он используется в общем коде и запускается из Kotlin / Native, тогда используйте InvalidMutabilityException на MainViewModel.

Похоже, причина этого в том, что все, что передается в withContext, рекурсивно замораживается, поэтому, поскольку objectWhichContainsNetworking является свойством MainViewModel и используется в withContext, тогда MainViewModel застревает.

Итак, мой вопрос: это просто ограничение текущей модели памяти Kotlin / Native? Или, может быть, текущая версия сопрограмм? И есть ли способы обойти это?

Примечание: версия сопрограмм: 1.3.9-native-mt. Котлин версии 1.4.0.


Изменить 1: похоже, что приведенный выше сокращенный код действительно работает нормально. Оказывается, инкриминирующий код был обновляемой переменной в модели представления (используемой для хранения ссылки на последнее состояние представления), которая замораживается, а затем выдает исключение при попытке мутировать. Я собираюсь попробовать использовать Flow / Channels, чтобы убедиться, что ссылка на var не требуется, и посмотрю, решит ли это общую проблему.

Примечание: если есть способ избежать зависания MainViewModel в первую очередь, это все равно будет фантастически!


Изменить 2: заменил var на Flow. Я не мог получить стандартный сбор потоков в iOS, пока не использовал здесь помощников:https://github.com/JetBrains/kotlinconf-app/blob/master/common/src/mobileMain/kotlin/org/jetbrains/kotlinconf/FlowUtils.kt.

MainViewModel по-прежнему зависает, но поскольку все его состояние неизменяемо, это больше не проблема. Надеюсь, это кому-то поможет!

Ответы

4 KevinGalligan Aug 19 2020 at 21:14

В исходном коде вы ссылаетесь на поле родительского объекта, что заставляет вас захватить весь родительский объект и заморозить его. Это не проблема сопрограмм. Сопрограммы подчиняются тем же правилам, что и все другие библиотеки параллелизма в Kotlin / Native. Он замораживает лямбду при пересечении потоков.

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

Чтобы этого не произошло:

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

Самое сложное с моделью памяти вот что. Привыкайте к тому, что снимается, и избегайте этого. Это не так уж и сложно, когда к этому привыкнешь, но нужно изучить основы.

Я говорил об этом подробно:

Практический Kotlin / собственный параллелизм

Kotlin Native Concurrency на практике

KotlinConf KN Concurrency

Модель памяти меняется, но до того, как она появится, пройдет немало времени. Как только вы привыкнете к модели памяти, неизменные проблемы обычно легко диагностировать.