edit : i strongly disagree with the closing, especially now that i found the answer. The question is very precise, the bonus questions are just bonus... The answer simply is :"you can't use List<T>
without locking because it is using versioning internally and is enforcing data-consistency". (i posted the answer but i have to wait 1 day before accepting it)
Context :
- I'm using NET Core 3.1 and C# in a WPF application.
- I have a lot of threads using either explicitly started Task and Parallel.ForEach, all reading and potentially modifying a static List.
- it is guaranteed that multiple different elements are modified by multiple thread at the same time BUT a single element will be only modified by single thread. (manual task do not modify the list, only Parallel.ForEach does, and i manage them carefully)
- i'm using a very minimalist amount of lock : only when i add/remove an element
- The method where the exception is raised is called by this code :
CompositionTarget.Rendering = New EventHandler(CompositionTargetRendering)
The problem :
- The main UI thread is doing absolutely nothing except reading the static list in a (non-parallel) foreach loop to visualise the list without locking it.
- I have an exception
System.InvalidOperationException : Collection was modified; enumeration operation may not execute;
saying that the Collection was modified while the main WPF thread was looping over it to visualise it. (which is true, i am doing it) - Even if i lock the list while add/removing element (which make sense and it's ok since it doesn't happen too often), it still crash with an exception.
- It require to add a lock on the main UI thread, but then my list would be more or less permanently locked by the UI thread and scalability will drop to nil.
Question : How to constantly read(-only) this list without raising an exception and without locking it ?
My temporary workaround : It is ok if the visualization isn't "perfectly accurate", that why i can afford to modify it while visualizing it.
So i'm visualizing a copy using
List<T>.ToArray
. It works for now, but as the objects stored in the list will grow (a lot) in the future, memory usage will be a problem (many gigabyte). I can't afford to double the memory usage just for this.
Bonus question :
i honestly don't understand why it only raise an exception here. I'm reading and writing the list all over the place without any lock and without problem, except in the WPF thread ?
And, from the workaround, isn't creating a copy an access to this list anyway ? Why doesn't it raise an exception every single time i read it concurrently ? Is it a WPF thing ?
If the EventHandler is the problem, and you know another way to manually refresh my XAML <Image>, i'm all ears. It's the only way i know.