使用协程的 Kotlin/Native 多线程

Posted

技术标签:

【中文标题】使用协程的 Kotlin/Native 多线程【英文标题】:Kotlin/Native multithreading using coroutines 【发布时间】:2020-12-08 14:53:49 【问题描述】:

我一直在尝试使用 kotlin 多平台,它很棒,但线程让我难以理解。线程之间的状态冻结在概念上是有意义的,并且在小对象或原语来回传递的简单示例中工作正常,但在现实世界的应用程序中我无法绕过 InvalidMutabilityException。

android 应用中获取以下通用代码 sn-p

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 上 pow InvalidMutabilityException。

原因似乎是在 withContext 中传递的任何内容都被递归冻结了,所以因为 objectWhichContainsNetworking 是 MainViewModel 的一个属性并在 withContext 中使用,然后 MainViewModel 被冻结了。

所以我的问题是,这只是当前 Kotlin/Native 内存模型的限制吗?或者也许是当前版本的协程?有没有办法解决这个问题?

注意:协程版本:1.3.9-native-mt。 kotlin 1.4.0 版。


编辑 1: 所以看起来上面精简的代码实际上工作得很好。事实证明,有罪的代码是视图模型中的一个可更新变量(用于保持对最后一个视图状态的引用),它被冻结,然后在它试图被改变时抛出异常。我将尝试使用 Flow/Channels 来确保不需要 var 引用,看看这是否能解决整体问题。

注意:如果有办法避免 MainViewModel 一开始就被冻结,那就太好了!


编辑 2: 用 Flow 替换了 var。在使用这里的帮助程序之前,我无法在 ios 中获得标准流量收集:https://github.com/JetBrains/kotlinconf-app/blob/master/common/src/mobileMain/kotlin/org/jetbrains/kotlinconf/FlowUtils.kt。

MainViewModel 仍然被冻结,但由于它的所有状态都是不可变的,因此不再是问题。希望它可以帮助某人!

【问题讨论】:

【参考方案1】:

在您的原始代码中,您正在引用父对象的一个​​字段,这会导致您捕获整个父对象并将其冻结。这不是协程的问题。协程遵循与 Kotlin/Native 中所有其他并发库相同的规则。当你跨线程时它会冻结 lambda。

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

内存模型最复杂的地方就是这个。习惯于捕捉并避免它。当你习惯它时并不难,但你需要学习基础知识。

我已经详细讨论过了:

Practical Kotlin/Native Concurrency

Kotlin Native Concurrency Hands On

KotlinConf KN Concurrency

内存模型正在发生变化,但要实现这一目标还需要相当长的时间。一旦习惯了内存模型,不可变问题通常很容易诊断。

【讨论】:

以上是关于使用协程的 Kotlin/Native 多线程的主要内容,如果未能解决你的问题,请参考以下文章

python多线程多进程协程的使用

对协程的理解

协程的概念

Golang 线程和协程的区别

协程的优点(Python)

一个简单的多进程+多线程+协程的例子