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 AMColton Idle
08/07/2022, 1:30 AMephemient
08/07/2022, 1:30 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 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 AMephemient
08/11/2022, 1:07 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.
if 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>>
then 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."
(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 AMephemient
08/11/2022, 2:57 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
appState.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 crashappState.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...indexOfTodo = appState.fullListOfTodos.indexOfFirst { it.id == todo.id }
to
indexOfTodo = appState.fullListOfTodos.toList().indexOfFirst { it.id == todo.id }
ephemient
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 mutated.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
either your list isn't being concurrently mutatedin my original SO question I still don't see how it's being concurrently mutated honestly.
ephemient
08/11/2022, 6:48 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 AMColton Idle
08/11/2022, 7:34 AMephemient
08/11/2022, 8:05 AMColton Idle
08/11/2022, 8:10 AMephemient
08/11/2022, 8:10 AMColton Idle
08/11/2022, 8:31 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 namesomeFlow.launchIn(scope)
is short for
scope.launch {
someFlow.collect()
}
Colton Idle
08/11/2022, 12:35 PM