在 iOS 上的 KMM 项目中使用后台线程时出现无效的可变性异常

问题描述

我正在处理一个 KMM 项目,目前正在开发一个模型层。为了处理数据,我计划像这样创建一个单例:

@ThreadLocal object Repository {

    private var dao: DataAccessObject? = null

    private val scope = Coroutinescope(dispatchers.Main)

    fun injectDao(dao: DataAccessObject) {
        scope.async {
            Repository.dao = dao
        }
    }

    suspend fun create(dataObjectType: TypeOfDataObject): DataObject? {
        var dataObject: DataObject? = null

        val job = scope.async {
            dataObject = dao?.create(dataObjectType = dataObjectType)
        }

        job.await()

        return dataObject
    }
}

在您看到的这种实现中,对数据库的请求是在 Main 线程中处理的,这非常不好。但它有效并且数据从函数正确返回。下一个明显的步骤是尝试在后台范围内运行它。为了做到这一点,我们应该重新声明 scope:

private val scope = Coroutinescope(dispatchers.Default)

当我们运行代码并从某个地方调用 create 函数

Uncaught Kotlin exception: kotlin.native.concurrent.InvalidMutabilityException: mutation attempt of frozen kotlin.native.internal.Ref@5761ad88
2021-02-02 23:54:50.408645+0300 Plendy[28960:2893398] [] nw_protocol_get_quic_image_block_invoke dlopen libquic Failed
    at 0   Shared                          0x000000010d51640f kfun:kotlin.Throwable#<init>(kotlin.String?){} + 95 (/Users/teamcity/buildAgent/work/f01984a9f5203417/runtime/src/main/kotlin/kotlin/Throwable.kt:23:37)
    at 1   Shared                          0x000000010d50f0bd kfun:kotlin.Exception#<init>(kotlin.String?){} + 93 (/Users/teamcity/buildAgent/work/f01984a9f5203417/runtime/src/main/kotlin/kotlin/Exceptions.kt:23:44)
    at 2   Shared                          0x000000010d50f32d kfun:kotlin.RuntimeException#<init>(kotlin.String?){} + 93 (/Users/teamcity/buildAgent/work/f01984a9f5203417/runtime/src/main/kotlin/kotlin/Exceptions.kt:34:44)
    at 3   Shared                          0x000000010d5448cd kfun:kotlin.native.concurrent.InvalidMutabilityException#<init>(kotlin.String){} + 93 (/Users/teamcity/buildAgent/work/f01984a9f5203417/runtime/src/main/kotlin/kotlin/native/concurrent/Freezing.kt:22:60)
    at 4   Shared                          0x000000010d5460af ThrowInvalidMutabilityException + 431 (/Users/teamcity/buildAgent/work/f01984a9f5203417/runtime/src/main/kotlin/kotlin/native/concurrent/Internal.kt:92:11)
    at 5   Shared                          0x000000010d6470b0 MutationCheck + 128
    at 6   Shared                          0x000000010d5640f8 kfun:kotlin.native.internal.Ref#<set-element>(1:0){} + 104 (/Users/teamcity/buildAgent/work/f01984a9f5203417/runtime/src/main/kotlin/kotlin/native/internal/Ref.kt:12:5)
    at 7   Shared                          0x000000010d4a1d5b kfun:com.plendy.PlendyCore.Model.KNPlendyData.$create$lambda-1COROUTINE$4.invokeSuspend#internal + 779 (/Users/petr/Documents/Projects/Plendy/Android/Plendy/PlendyCore/src/commonMain/kotlin/com/plendy/PlendyCore/Model/KNPlendyData.kt:23:13)
    at 8   Shared                          0x000000010d537958 kfun:kotlin.coroutines.native.internal.BaseContinuationImpl#resumeWith(kotlin.Result<kotlin.Any?>){} + 760 (/Users/teamcity/buildAgent/work/f01984a9f5203417/runtime/src/main/kotlin/kotlin/coroutines/ContinuationImpl.kt:30:39)
    at 9   Shared                          0x000000010d6d7a78 kfun:kotlinx.coroutines.dispatchedTask#run(){} + 2680 (/opt/buildAgent/work/44ec6e850d5c63f0/kotlinx-coroutines-core/common/src/internal/dispatchedTask.kt:106:71)
    at 10  Shared                          0x000000010d687fb8 kfun:kotlinx.coroutines.EventLoopImplBase#processNextEvent(){}kotlin.Long + 840 (/opt/buildAgent/work/44ec6e850d5c63f0/kotlinx-coroutines-core/common/src/EventLoop.common.kt:274:18)
    at 11  Shared                          0x000000010d6efbbb kfun:kotlinx.coroutines#runEventLoop(kotlinx.coroutines.EventLoop?;kotlin.Function0<kotlin.Boolean>){} + 843 (/opt/buildAgent/work/44ec6e850d5c63f0/kotlinx-coroutines-core/native/src/Builders.kt:80:40)
    at 12  Shared                          0x000000010d6f8d39 kfun:kotlinx.coroutines.WorkerCoroutinedispatcherImpl.start$lambda-0#internal + 409 (/opt/buildAgent/work/44ec6e850d5c63f0/kotlinx-coroutines-core/native/src/Workers.kt:49:17)
    at 13  Shared                          0x000000010d6f8f30 kfun:kotlinx.coroutines.WorkerCoroutinedispatcherImpl.$start$lambda-0$FUNCTION_REFERENCE$35.invoke#internal + 64 (/opt/buildAgent/work/44ec6e850d5c63f0/kotlinx-coroutines-core/native/src/Workers.kt:47:24)
    at 14  Shared                          0x000000010d6f8f90 kfun:kotlinx.coroutines.WorkerCoroutinedispatcherImpl.$start$lambda-0$FUNCTION_REFERENCE$35.$<bridge-UNN>invoke(){}#internal + 64 (/opt/buildAgent/work/44ec6e850d5c63f0/kotlinx-coroutines-core/native/src/Workers.kt:47:24)
    at 15  Shared                          0x000000010d545d59 WorkerLaunchpad + 185 (/Users/teamcity/buildAgent/work/f01984a9f5203417/runtime/src/main/kotlin/kotlin/native/concurrent/Internal.kt:69:54)
    at 16  Shared                          0x000000010d64ba4f _ZN6Worker19processQueueElementEb + 3135
    at 17  Shared                          0x000000010d64adf6 _ZN12_GLOBAL__N_113workerRoutineEPv + 54
    at 18  libsystem_pthread.dylib             0x0000000110a86950 _pthread_start + 224
    at 19  libsystem_pthread.dylib             0x0000000110a8247b thread_start + 15

奇怪的是,数据写入db,意味着dao调用成功,但是数据没有从函数返回,因为异常发生得更早。此时我不明白异常与什么冻结对象有关?我接下来尝试的是删除 job.await() 行,它完美地工作,除了函数输出中的 null 原因之外没有任何例外。

所以我的问题是:如何让代码后台线程中运行仍然能够等待其输出

解决方法

您应该包含更多异常信息以帮助弄清楚发生了什么,并且您可以使用 ensureNeverFrozen 来帮助确定某些内容何时被无意冻结。不过,在这种情况下,我想我可以弄清楚。

在这种情况下,在后台 lambda 中捕获对 dataObject 的引用将冻结它。尝试重新分配它(可能)会抛出您的异常。

var dataObject: DataObject? = null

        val job = scope.async {
            //Trying to assign the frozen dataObject will fail
            dataObject = dao?.create(dataObjectType = dataObjectType)
        }

既然您已经在挂起函数中,为什么不直接使用 withContext 之类的东西?

suspend fun create(dataObjectType: TypeOfDataObject): DataObject? {

        val dataObject = withContext(Dispatchers.Default) {
            dao?.create(dataObjectType = dataObjectType)
        }

        return dataObject
    }

如果你走那么远......

suspend fun create(dataObjectType: TypeOfDataObject): DataObject? = withContext(Dispatchers.Default) {
    dao?.create(dataObjectType = dataObjectType)
}

相关问答

Selenium Web驱动程序和Java。元素在(x,y)点处不可单击。其...
Python-如何使用点“。” 访问字典成员?
Java 字符串是不可变的。到底是什么意思?
Java中的“ final”关键字如何工作?(我仍然可以修改对象。...
“loop:”在Java代码中。这是什么,为什么要编译?
java.lang.ClassNotFoundException:sun.jdbc.odbc.JdbcOdbc...