Colton Idle
08/07/2022, 1:21 AMwithContext(Dispatchers.Default){
but now I get a ConcurrentModificationException
. I could understand if I was adding/removing to the list on Dispatchers.Default while main thread was swapping the list entirely... but I'm just searching via indexOfFirst
. thoughts?ephemient
08/07/2022, 1:28 AMephemient
08/07/2022, 1:29 AMColton Idle
08/07/2022, 1:30 AMColton Idle
08/07/2022, 1:30 AMephemient
08/07/2022, 1:30 AMephemient
08/07/2022, 1:31 AMColton Idle
08/07/2022, 1:33 AMval indexOfPlace = appState.fullList.indexOfFirst { it.id == placeUid }
but it led to the CME. I wonder what the best thing to do here is. I really didn't think that reading a list while it's being modified would cause an issue.ephemient
08/07/2022, 1:35 AMColton Idle
08/07/2022, 1:36 AMColton Idle
08/07/2022, 1:37 AMephemient
08/07/2022, 1:39 AMCopyOnWriteArrayList
which (as the name implies) creates a copy on every modification, so existing iterators are not invalidated (although they will continue operating on a stale copy)Colton Idle
08/07/2022, 1:56 AMColton Idle
08/10/2022, 9:40 PMephemient
08/11/2022, 1:07 AMephemient
08/11/2022, 1:35 AMMap
to eliminate the need to scan with indexOfFirst
for each item)Colton Idle
08/11/2022, 2:28 AMyou can only read a data structure in multiple threads if it's immutable or if it's known to be thread-safe (which List isn't)TIL. I thought that you can red a data structure in multiple threads if you basically know when each thread is calling which. which in this case I thought that'd be safe because even though im switching threads, im trying to be procedural about the whole thing.
Colton Idle
08/11/2022, 2:29 AMif you can makeokay. that makes sense. So the list is immutable, but I can change the list that I'm referencing overall from other threads?into aappState.fullListOfTodos
, then that is safe to update from other threads.MutableState<List<Model>>
Colton Idle
08/11/2022, 2:30 AMthen you just keep everything else immutable, and process all the mutation on a single threadwait. so doesn't this just go against what you just said of "then that is safe to update from other threads."
Colton Idle
08/11/2022, 2:32 AM(I've also taken the liberty of using ayeah. I was definitely getting to the point where I wanted to use a map. but as an excercise I wanted to see how I can do this performantly with just a list. as my crux of the issue has been that indexOfFirst is slow (of course) with a large list, and so I was like "oh wait. coroutines makes this a piece of cake"to eliminate the need to scan withMap
for each item)indexOfFirst
ephemient
08/11/2022, 2:50 AMColton Idle
08/11/2022, 2:55 AMColton Idle
08/11/2022, 2:55 AMephemient
08/11/2022, 2:57 AMephemient
08/11/2022, 2:59 AMList(100) { SomeBigObject() }.toList()
results in two lists (at least temporarily), each with a backing array of size (at least) 100, but there's only 100 SomeBigObject
instances across themColton Idle
08/11/2022, 3:03 AMesults in two lists (at least temporarily), each with a backing array of size (at least) 100, but there's only 100🤯 why hasn't that occurred to me? lol. okay cool. wow. so many TILs.instances across themSomeBigObject
Colton Idle
08/11/2022, 3:05 AMappState.fullListOfTodos
into a MutableState<List<Model>>
"
I really like your solution you outlined here BUT out of curiosity I just want to see if I can make my current code not crashColton Idle
08/11/2022, 3:06 AMColton Idle
08/11/2022, 4:33 AMappState.fullListOfTodos
into a MutableState<List<Model>>
2. Switched
//this search for index could take a long time, so move to CPU bound Dispatcher
withContext(Dispatchers.Default) {
// The crash/exception happens on this line VVV
indexOfTodo =
appState.fullListOfTodos.indexOfFirst { it.id == todo.id }
place = appState.fullListOfTodos[indexOfTodo]
updatedTodo = TodoModel(//update a few fields)
}
// If I remove this line, the crash/exception does not happen VV
appState.fullListOfTodos[indexOfTodo] = updatedTodo
to
withContext(Dispatchers.Default) {
indexOfTodo =
appState.fullListOfTodos.indexOfFirst { it.id == todo.id }
place = appState.fullListOfTodos[indexOfTodo]
updatedTodo = TodoModel(//update a few fields)
val newList = appState.fullListOfTodos.toMutableList().apply { this[indexOfTodo] = updatedTodo }
appState.fullListOfTodos = newList
}
How does that sound to you? (again with the premise that I will be updating my code to your other suggestion i.e. Flowable and using a Map) but if I wanted to "solve" this just as practice then I think this makes sense...Colton Idle
08/11/2022, 4:47 AMindexOfTodo = appState.fullListOfTodos.indexOfFirst { it.id == todo.id }
to
indexOfTodo = appState.fullListOfTodos.toList().indexOfFirst { it.id == todo.id }
Colton Idle
08/11/2022, 4:48 AMephemient
08/11/2022, 4:52 AM.toList()
there doesn't make a difference. it creates another copy, but that requires iterating and reading the list just the same as .indexOfFirst
does, and will fail if the list is concurrently mutatedephemient
08/11/2022, 4:55 AM.toList().indexOfFirst {...}
and .indexOfFirst {...}
are safe), or your list is being concurrently mutated (in which case both .toList().indexOfFirst {...}
and .indexOfFirst {...}
are unsafe). if anything, .toList
has about a double chance of being unsafe on average, since it always iterates the whole list instead of stopping when .indexOfFirst
finds the first match (assuming random distribution)Colton Idle
08/11/2022, 6:09 AMdamn. maybe its just happening so quickly that its not triggering a CME in my testing. thanks for the heads up on that.there doesn't make a difference. it creates another copy, but that requires iterating and reading the list just the same as.toList()
does, and will fail if the list is concurrently mutated.indexOfFirst
Colton Idle
08/11/2022, 6:11 AMeither your list isn't being concurrently mutatedin my original SO question I still don't see how it's being concurrently mutated honestly.
Colton Idle
08/11/2022, 6:12 AMephemient
08/11/2022, 6:48 AMephemient
08/11/2022, 6:51 AMColton Idle
08/11/2022, 6:53 AMephemient
08/11/2022, 6:55 AMColton Idle
08/11/2022, 6:55 AMappState.fullListOfTodos
into a MutableState<List<Model>>
2. Switch
//this search for index could take a long time, so move to CPU bound Dispatcher
withContext(Dispatchers.Default) {
// The crash/exception happens on this line VVV
indexOfTodo =
appState.fullListOfTodos.indexOfFirst { it.id == todo.id }
place = appState.fullListOfTodos[indexOfTodo]
updatedTodo = TodoModel(//update a few fields)
}
// If I remove this line, the crash/exception does not happen VV
appState.fullListOfTodos[indexOfTodo] = updatedTodo
to
withContext(Dispatchers.Default) {
indexOfTodo =
appState.fullListOfTodos.indexOfFirst { it.id == todo.id }
place = appState.fullListOfTodos[indexOfTodo]
updatedTodo = TodoModel(//update a few fields)
val newList = appState.fullListOfTodos.toMutableList().apply { this[indexOfTodo] = updatedTodo }
appState.fullListOfTodos = newList
}
ephemient
08/11/2022, 6:59 AMephemient
08/11/2022, 7:06 AMephemient
08/11/2022, 7:09 AMephemient
08/11/2022, 7:11 AMColton Idle
08/11/2022, 7:34 AMColton Idle
08/11/2022, 7:36 AMColton Idle
08/11/2022, 7:59 AMephemient
08/11/2022, 8:05 AMColton Idle
08/11/2022, 8:10 AMephemient
08/11/2022, 8:10 AMephemient
08/11/2022, 8:10 AMephemient
08/11/2022, 8:12 AMephemient
08/11/2022, 8:13 AMephemient
08/11/2022, 8:16 AMColton Idle
08/11/2022, 8:31 AMColton Idle
08/11/2022, 8:34 AMColton Idle
08/11/2022, 8:35 AMephemient
08/11/2022, 12:24 PMColton Idle
08/11/2022, 12:26 PMephemient
08/11/2022, 12:32 PMsomeFlow.runningFold(initial, transform)
is short for
flow {
var accumulator = initial
emit(accumulator)
someFlow.collect { value ->
accumulator = transform(accumulator, value)
emit(accumulator)
}
}
same as the Iterable extension by the same nameephemient
08/11/2022, 12:33 PMsomeFlow.launchIn(scope)
is short for
scope.launch {
someFlow.collect()
}
Colton Idle
08/11/2022, 12:35 PM