组织 Kotlin 的协程

Organizing Kotlin's Coroutines

我经常问自己如何使用协程。每次单击按钮或出现其他事件时,我都会启动一个协程来保存或加载数据 from/to 数据库或休息 api。然后我有像下面这样的小功能。

在极少数情况下,如果同时有两个或多个协程 write/read,我会得到 ConcurrentModificationExceptions。我从来没有遇到 Java+RxJava 这个问题。现在我只使用 Kotlin+Coroutines(没有 RxKotlin,没有 Flow,没有 LiveData)。作为数据库,我使用 Room。

有没有办法保存对协程容器之类的东西的引用,我可以在其中添加作业来一个接一个地完成它们?或者你们实际上是如何启动协程的?

fun loadAllDataForTheUserInterface() {
    viewModelScope.launch {
        val newData = dataBaseRepository.load(...)
        fragment.draw(newData)
    }
}

fun handleSaveClick(user: User) {
    viewModelScope.launch {
        val newUser = restApiRepository.uploadNewUser(user)
        databaseRepository.save(newUser)
        fragment.close()
    }
}

我的堆栈跟踪

java.util.ConcurrentModificationException
    at java.util.ArrayList$Itr.next(ArrayList.java:860)
    at com.google.gson.internal.bind.CollectionTypeAdapterFactory$Adapter.write(CollectionTypeAdapterFactory.java:96)
    at com.google.gson.internal.bind.CollectionTypeAdapterFactory$Adapter.write(CollectionTypeAdapterFactory.java:61)
    at com.google.gson.Gson.toJson(Gson.java:704)
    at com.google.gson.Gson.toJson(Gson.java:683)
    at com.google.gson.Gson.toJson(Gson.java:638)
    at com.google.gson.Gson.toJson(Gson.java:618)
    at my.supercool.app.component.module.JsonModule.toJson(JsonModule.kt:12)
    at my.supercool.app.data.database.MyRoomObjectConverter.fromObjectToJson(MyRoomObjectConverter.kt:29)
    at my.supercool.app.data.database.MyRoomObjectConverter.fromSomeListToJson(MyRoomObjectConverter.kt:71)
    at my.supercool.app.data.database.dao.SomeDao_Impl.bind(SomeDao_Impl.java:101)
    at my.supercool.app.data.database.dao.SomeDao_Impl.bind(SomeDao_Impl.java:47)
    at androidx.room.EntityInsertionAdapter.insertAndReturnId(EntityInsertionAdapter.java:113)
    at my.supercool.app.data.database.dao.SomeDao_Impl.call(SomeDao_Impl.java:142)
    at my.supercool.app.data.database.dao.SomeDao_Impl.call(SomeDao_Impl.java:137)
    at androidx.room.CoroutinesRoom$Companion$execute.invokeSuspend(CoroutinesRoom.kt:61)
    at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
    at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:106)
    at androidx.room.TransactionExecutor.run(TransactionExecutor.java:47)
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1167)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:641)
    at java.lang.Thread.run(Thread.java:923)

对于您给出的示例,我认为演员模型会很好用。在 actor 模型中,您有一个 运行 循环执行 运行 某些任务的协程。系统的其他部分在有工作要做时会向 actor 发送消息。

首先,定义一个actor。 actor 协程构建器函数启动一个协程,它将 运行 我们的任务,并创建一个 Channel 我们可以用来向它发送任务。

val viewModelTasks = viewModelScope.actor<() -> Unit> {
    for (task in this) {
        task.invoke()
    }
}

当您有任务要执行时,将其发送给参与者:

val myTask = { doSomeWork() }
viewModelTasks.send(myTask)

Actor 将循环执行它收到的任务,在开始下一个任务之前完成每个任务。

因为 actor 是单个协程,并且一次只会 运行 一个任务,这是保护资源免受不同线程并发访问的好方法。

不过,Actor 不太适合需要获取任务结果或等待任务完成的情况。如果您需要这些东西,您可能需要考虑不同的解决方案。

我发现这个问题的实际解决方案是调用 withContext 块中的代码。这样你的协程将一个接一个地执行,不会相互竞争或更糟,就像我的情况一样,编辑相同的资源并导致错误。

如果我发现它究竟是如何工作的以及如何使用它,我会尽快编辑这个答案。现在这里有一个 link 以 https://www.baeldung.com/kotlin/withcontext-vs-async-await

开头