组织 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
开头
我经常问自己如何使用协程。每次单击按钮或出现其他事件时,我都会启动一个协程来保存或加载数据 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
开头