Yes, this exception suggests that you really miss the lock. I mean, yes, your understanding that lock is impractical is basically valid (they say: "best synchronization is no synchronization"). The problem is: you still do the operation which needs locking, so the solution will be: either 1) add lock on the same lock object to the statement; 2) or avoid such operation. Another approach, which you will eventually need anyway, with this or another problem, is combined: use a lock but minimize its use.
I hope you understand how to use the lock, so I want to set aside this trivial issue. Your exception suggests that the failure to lock was manifested when you tried to modify the collection. What does it mean? First, let's consider the simple model without locking at all. Let's say, you have one "main" thread (I say nothing about its nature; it does not have to be the UI thread or the thread of your application entry point, it can be anything) used to manipulate collection, and N thread, each corresponding the a collection element, to manipulate elements.
What would it mean, "manipulate collection" and "manipulate elements"? First, the elements. Each thread corresponding to an element, can modify the state of this element's object and never anything else. Importantly, this object is of the reference type (you said, "classes", so everything is fine), and this element's thread
should not modify the referential identity of the element object. Naturally, such thread should not add/remove and
even access elements of collection,
they should not even traverse the elements, inquire the collection length.
And the main thread should add/remove elements, and do everything with the collection,
but never access element objects, not even read them. The element's thread can be started only when the element is created, and the element can be removed, without stopping the thread. That is, adding and removing elements can be done when elements' threads are executing; this is guaranteed by the rule of not touching referential identities. For collaboration, lock is still required. For example, the main thread can iterate the elements. But what to do with each one? As soon as elements members need to get involved, an interlock with the element's thread is required.
One modification of these scheme could be using
upgradeable lock
System.Threading.ReaderWriterLockSlim
:
https://msdn.microsoft.com/en-us/library/system.threading.readerwriterlockslim%28v=vs.110%29.aspx[
^].
It's use goes beyond the lock-less schema, so please learn the performance implications yourself. This are understandable but not so trivial.
And yes, you probably can simplify your code while preserving thread safety. One of the approaches I use all the time is the concept of
thread wrapper I used well before .NET (I am the author of one of the earliest implementations of threads, when they weren't even introduced in C++ yet). I presented this idea in my past answers:
How to pass ref parameter to the thread[
^],
Change parameters of thread (producer) after it is started[
^] (this is how to encapsulate and hide locking, by the way),
http://www.codeproject.com/Answers/485734/MultiThreadingplusinplusC-23#answer4[
^],
Making Code Thread Safe[
^].
Main point of the wrapper is the access of the members from a thread and isolating and encapsulation of this access. The idea is using an instance (non-static) thread method and getting access through "this", while keeping control over the access.
—SA