コルーチンを使用した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から実行される場合は、MainViewModelでInvalidMutabilityExceptionを実行します。

これは、withContextで渡されたものがすべて再帰的にフリーズされるため、objectWhichContainsNetworkingがMainViewModelのプロパティであり、withContextで使用されるため、MainViewModelがフリーズに巻き込まれるためと思われます。

だから私の質問は、これは現在のKotlin / Nativeメモリモデルの単なる制限ですか?それとも、コルーチンの現在のバージョンですか?そして、これを回避する方法はありますか?

注:コルーチンバージョン:1.3.9-native-mt。kotlinバージョン1.4.0。


編集1:したがって、上記のスリム化されたコードは実際には正常に機能しているようです。識別コードは、ビューモデル(最後のビュー状態への参照を保持するために使用)の更新可能な変数であり、フリーズし、変更しようとすると例外をスローすることが判明しました。Flow / Channelsを使用して、var参照が不要であることを確認し、これで全体的な問題が解決するかどうかを確認します。

注:そもそもMainViewModelがフリーズするのを回避する方法があれば、それでも素晴らしいでしょう!


編集2:変数をフローに置き換えました。ここでヘルパーを使用するまで、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ネイティブ同時実行ハンズオン

KotlinConfKN同時実行性

メモリモデルは変化していますが、それが実現するまでにはかなりの時間がかかります。メモリモデルに慣れれば、不変の問題は一般的に簡単に診断できます。