My use case is an ordered collection of items (I need to preserve the order in which they've arrived) which is mutable (sometimes, albeit rarely, an item may be removed), which may be modified by different threads (Add/Remove), where the final enumeration is done by a single thread.
There is no collection in System.Collections.Concurrent or other standard namespaces that implement Remove<T>(T item) or RemoveAt(int index). Thus none of them suits me.
I think I'm not alone in this, for example #18725, however I expect that many people just wrap List<T> with lock. That's sufficient in low- to medium-level concurrency, which I aim at.
Hence I call for un-deprecating of SynchronizedCollection<T> and adding it to the standard library.
Tagging @eiriktsarpalis as an area owner. If you would like to be tagged for a label, please notify danmosemsft.
SynchronizedCollection<T> is actually available in .NET Core, in the System.ServiceModel.Primitives package.
However, to your point, have you looked at Immutable Collections? They are designed to be immutable data structures that are safe to use and update concurrently. For example, you could safely do RemoveAt concurrently with an ImmutableArray like so:
ImmutableInterlocked.InterlockedExchange(
ref immutableArray,
immutableArray.RemoveAt(index));
SynchronizedCollection
is actually available in .NET Core, in the System.ServiceModel.Primitives package.
Thank you... I tried to find the package but failed because there are multiple System.ServiceModel.* packages. The docs are only available for .NET Framework 4.8, not .NET Core and they mention System.ServiceModel.dll...
As for Immutable Collections, they're quite fast, I'll look into them, thanks!
.... although note that RemoveAt(int index) is _incredibly_ unthreadsafe if multiple threads are removing items.
@Clockwork-Muse That's why I needed SynchronizedCollection<T> or just a lock around the List<T>. I tested the concurrency of adding and removing data using 100,000 tasks and it meets my expectations.
An explicit lock around a list works fine, but must be used consistently and forgetting it leads to the nastiest bugs.
Both solutions exhibit similar performance which is excellent. The difference is that SynchronizedCollection<T> is a perfect packaged tool which developer has less chances to use wrong. And I mean the followers who might be involved. Better be safe than sorry.
And I really need that order preservation :)
@Hermholtz I think the point is that RemoveAt both depends on the index of items and changes the index of items, so you have to be very careful with how you use it. Consider this example of having a SynchronizedCollection<int> and then removing all even numbers from it using RemoveAt:
```c#
var collection = new SynchronizedCollection
for (int i = 0; i < 1000; i++)
{
collection.Add(i);
}
Parallel.For(0, 500, i =>
{
var index = collection.IndexOf(i * 2);
collection.RemoveAt(index);
});
```
This code is not thread-safe, because the index of an item can change between the two lines in the Parallel.For, resulting in exceptions or, worse, incorrect results
To do this correctly, you would have to execute both lines under the same lock.
@svick fantastic example :) I wouldn't do it, but can imagine somebody doing so. I get your point: using SynchronizedCollection itself is not enough, still some thinking is required. Thanks!!