sta*_*cks 2 kotlin kotlin-coroutines android-jetpack-compose
我有(我认为的)一个非常简单的概念,我可以在其中刷新待办事项列表的详细信息。我发现,如果有足够的 TODO 项目(几千个)并且按下刷新按钮(因此fetchFreshTodoItemDetails
重复调用),那么我会因以下异常而崩溃:
java.util.ConcurrentModificationException
at androidx.compose.runtime.snapshots.StateListIterator.validateModification(SnapshotStateList.kt:278)
at androidx.compose.runtime.snapshots.StateListIterator.next(SnapshotStateList.kt:257)
at com.rollertoaster.app.ui.screens.todo.TodoScreenViewModel$fetchFreshTodoItemDetails$1$1$1.invokeSuspend(TodoScreenViewModel.kt:332)
at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:106)
at kotlinx.coroutines.scheduling.CoroutineScheduler.runSafely(CoroutineScheduler.kt:570)
at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.executeTask(CoroutineScheduler.kt:750)
at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.runWorker(CoroutineScheduler.kt:677)
at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.run(CoroutineScheduler.kt:664)
Suppressed: kotlinx.coroutines.DiagnosticCoroutineContextException: [StandaloneCoroutine{Cancelling}@97be452, Dispatchers.Main.immediate]
Run Code Online (Sandbox Code Playgroud)
我的视图模型:
java.util.ConcurrentModificationException
at androidx.compose.runtime.snapshots.StateListIterator.validateModification(SnapshotStateList.kt:278)
at androidx.compose.runtime.snapshots.StateListIterator.next(SnapshotStateList.kt:257)
at com.rollertoaster.app.ui.screens.todo.TodoScreenViewModel$fetchFreshTodoItemDetails$1$1$1.invokeSuspend(TodoScreenViewModel.kt:332)
at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:106)
at kotlinx.coroutines.scheduling.CoroutineScheduler.runSafely(CoroutineScheduler.kt:570)
at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.executeTask(CoroutineScheduler.kt:750)
at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.runWorker(CoroutineScheduler.kt:677)
at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.run(CoroutineScheduler.kt:664)
Suppressed: kotlinx.coroutines.DiagnosticCoroutineContextException: [StandaloneCoroutine{Cancelling}@97be452, Dispatchers.Main.immediate]
Run Code Online (Sandbox Code Playgroud)
尽管我可以想出几种方法来解决这个问题...我仍然“认为”上述方法应该有效,但事实并非如此,这让我有点疯狂。感谢任何帮助。谢谢
编辑: fullListOfTodos 在我的 appStateHolder 中定义
var fullListOfTodos = mutableStateListOf<TodoModel>()
正如您所提到的,在列表中进行搜索indexOfFirst
需要很长时间。迭代器的实现禁止其并发修改,这意味着您在迭代搜索时不允许更改列表项。此处发生崩溃是因为列表在循环时被修改,产生了ConcurrentModificationException
.
您现在可能认为列表在迭代时不可能被修改,因为您的代码是顺序执行的,因此修改是在搜索之后完成的。fetchFreshTodoItemDetails
当您多次拨打电话时,就会出现此问题。它创建另一个协程来执行(可能同时)搜索和修改列表的代码。所以基本上,函数调用会互相竞争,如果运气不好的话会互相中断。
此时,您可能会认为您实现了一种机制,该机制不允许两个协程使用变量并行运行,fetchJob
该变量保存一个作业,该作业在调用函数时被取消,然后启动一个新作业。这里的问题是你的假设是错误的。
当您调用 时fetchJob?.cancel()
,您基本上会向当前正在执行的协程发送一个取消信号。由于取消是协作性的,协程必须到达暂停点才能取消,这在indexOfFirst
调用中不会发生。由于fetchJob?.cancel()
不等待完成Job
,viewModelScope.launch
因此可能会在前一个Job
完成之前执行,因此与最后一个调用并行执行。垃圾邮件按钮会导致创建许多协程,并在修改列表项时相互破坏。
既然您知道了问题所在,那么您很可能可以在阅读更多文档后提出自己的解决方案(我强烈建议,Kotlin 协程指南非常棒)。
您可能会想到一些快速修复方法(我不推荐):
yield()
或coroutineContext.isActive
来使其与取消机制配合。在这里,您仍然面临并发执行的风险,但风险较低。fetchJob?.cancelAndJoin()
为了真正等待最后一个协程完成,但这只会堆积请求并需要将函数标记为suspend
,或者您需要启动两个嵌套协程。我的推荐受到演员模式的启发。您本质上将有一个Channel
可以发送信号的地方,即更新数据的意图。该意图将被无限期运行的协程消耗,并且每当出现新意图时才开始搜索过程,但前提是最后一个操作完成。
class MyViewModel : ViewModel() {
private val updateChannel = Channel<List<Long>>(capacity = 1, onBufferOverflow = BufferOverflow.DROP_OLDEST)
init {
viewModelScope.launch(Dispatchers.Default) {
updateChannel.consumeEach { ids ->
searchAndReplace(ids)
}
}
}
fun fetchFreshTodoItemDetails(idsToRefresh: List<Long>) {
updateChannel.trySend(idsToRefresh)
}
private suspend fun searchAndReplace(ids: List<Long>) {
TODO("The logic you had previously")
}
}
Run Code Online (Sandbox Code Playgroud)
您当然可以根据您的用例进行调整。希望这对您有用,并让您对当前的问题有一个新的视角。
归档时间: |
|
查看次数: |
2451 次 |
最近记录: |