Thread Safe Collections in .Net

Thread safe collections in .NET

The .NET 4.0 Framework introduces several thread-safe collections in the System.Collections.Concurrent Namespace:

ConcurrentBag<T>
      Represents a thread-safe, unordered collection of objects.

ConcurrentDictionary<TKey, TValue>
    Represents a thread-safe collection of key-value pairs that can be accessed by multiple threads concurrently.

ConcurrentQueue<T>
    Represents a thread-safe first in-first out (FIFO) collection.

ConcurrentStack<T>
    Represents a thread-safe last in-first out (LIFO) collection.


Other collections in the .NET Framework are not thread-safe by default and need to be locked for each operation:

lock (mySet)
{
mySet.Add("Hello World");
}

When to lock a thread-safe collection in .net ? ( & when not to lock ? )

You should not lock a thread safe collection, it exposes methods to update the collection that are already locked, use them as intended.

The thread safe collection may not match your needs for instance if you want to prevent modification while an enumerator is opened on the collection (the provided thread safe collections allow modifications). If that's the case you'd better use a regular collection and lock it everywhere. The internal locks of the thread safe collections aren't publicly available.

It's hard to answer about implication in not locking a thread-safe collection. You don't need to lock a thread-safe collection but you may have to lock your code that does multiple things. Hard to tell without seeing the code.


Yes the method is thread safe but it might call the AddValueFactory multiple times if you hit an Add for the same key at the same time. In the end only one of the values will be added, the others will be discarded. It might not be an issue... you'll have to check how often you may reach this situation but I think it's not common and you can live with the performance penalty in an edge case that may never occur.

You could also build your dictionnary in a static ctor or before you need it. This way, the dictionnary is filled once and you don't ever write to it. The dictionary is then read only and you don't need any lock neither a thread safe collection.

How Synchronized and Concurrent Collections are thread-safe but their content not

tl;dr

The objects stored in a thread-safe collection can be leaked outside and used in a non-thread-safe manner.

Detailed Answer:

I thought if Collection is thread-safe then its content will
implicitly be thread-safe, I mean if two threads cannot access my
Collection object then the object which my Collection object is
holding will implicitly become thread-safe.

I know sure I missing the point, could someone please explain me with
an example.

Consider the following code that uses two threads to add to the same non-thread-safe list the elements from 0 to 10. In the end, the main thread sums all the elements of that list. The final result should be same as 0 + 0 + 1 + 1 + ... 9 + 9 = 90. However, if you execute the code a couple of times you get different values, and sometimes even the following NPE:

Exception in thread "main" java.lang.NullPointerException
at java.base/java.util.stream.ReduceOps$1ReducingSink.accept(ReduceOps.java:80)
at java.base/java.util.ArrayList$ArrayListSpliterator.forEachRemaining(ArrayList.java:1655)
at java.base/java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:484)
at java.base/java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:474)
at java.base/java.util.stream.ReduceOps$ReduceOp.evaluateSequential(ReduceOps.java:913)
at java.base/java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:234)
at java.base/java.util.stream.ReferencePipeline.reduce(ReferencePipeline.java:553)
at Z.CollectionThreadSafe.main(CollectionThreadSafe.java:26)

All this is the result of the race-condition during the call of the method add.

private static void addToList(List<Integer> list) {
for (int i = 0; i < 10; i++)
list.add(i);
}

public static void main(String[] arg) throws InterruptedException {
final int TOTAL_THREADS = 2;
List<Integer> list = new ArrayList<>();
ExecutorService pool = Executors.newFixedThreadPool(TOTAL_THREADS);
for (int i = 0; i < TOTAL_THREADS; i++) {
pool.submit(() -> addToList(list));
}
pool.shutdown();
pool.awaitTermination(10, TimeUnit.SECONDS);
System.out.println(list.stream().reduce(0, Integer::sum));
}

Let us fix the race-condition by using a thread-Safe List by calling Collections.synchronizedList. So let us adapt the previous code to:

List<Integer> list = Collections.synchronizedList(new ArrayList<>());

You can run it as many times as you want; the final result is always the same i.e., 90. That much we knew already. Let us showcase the:

It's worth mentioning that synchronized and concurrent collections
only make the collection itself thread-safe and not the contents.

You just need to adapt the previous code from:

List<Integer> list = Collections.synchronizedList(new ArrayList<>());

to:

final List<List<Integer>> LIST_THREAD_SAFE = Collections.synchronizedList(new ArrayList<>());
LIST_THREAD_SAFE.add(new ArrayList<>());
List<Integer> list = LIST_THREAD_SAFE.get(0);
...

and voilá! you have exactly the same situation as the first example that we have showcased (i.e., race-condition). Even though the list LIST_THREAD_SAFE is thread-safe its content is not. Hence,

synchronized and concurrent collections only make the collection
itself thread-safe and not the contents.

Extreme Thread Safe Collection

The simple answer is that you're using the wrong container.

ConcurrentBag isn't general-purpose. It is intended to be used more like a pool of reusable objects that you might (usually as a last step) reduce to a single non-concurrent value. One such problem it could be used for is to sum up a list concurrently.

If your primary usage of ConcurrentBag strays from add/remove, and you're enumerating the collection frequently, then you're using it wrong.

If you post more code, you'll get more targeted help. Concurrency is one of those areas where understanding the problem is very important to provide a performant solution.

Edit:

ConcurrentDictionary will work for what you're doing. The trick is that you don't want to use ConcurrentDictionary.Values -- this will lock the dictionary and copy its contents. If you just use its IEnumerable<T> interface, you'll be fine. For instance:

private ConcurrentDictionary<int,Student> _students;

public static IEnumerable<Student> GetStudents()
{
return _students.Select(x => x.Value);
}

public static Student GetStudentByID(int id)
{
Student s;
if(_students.TryGetValue(id, out s)) return s;

s = getStudentFromDb(id);
_students[id] = s;

return s;
}

Make custom C# collection thread-safe

If I understand correctly, you only have a single thread modifying the collection and just want to make sure the readers can access it safely.

If so, you can have your background thread compute new elements, but dispatch adding the elements to your reader thread. This effectively makes mutating/reading all take place on the same thread, avoiding the issue of synchronization. I believe this is what Servy was suggesting in the comments.

This is easiest if you're reading from the UI thread, as you can use the standard dispatching mechanisms. Async/await makes this particularly easy (assuming FillList is called from the UI thread):

private async Task  FillList()
{
gdpList = await Task.Run(() => SimpleIoc.Default.GetInstance<ICrud<gdp_groupe>>().GetAll().ToList());
MedecinGDP.AddRange(await Task.Run(() => SimpleIoc.Default.GetInstance<ICrud<vue_medecin>>().GetAll()));
CodeGDP_Collection.AddRange(gdpList);
FiltredParticipant.AddRange(await Task.Run(() => SimpleIoc.Default.GetInstance<ICrud<fsign_fiche>>().GetAll()));
}

concurrent access of list in c#

Manipulating objects referenced from the list in concurrent threads can cause conflicts in objects regarding state of the object. Manipulating items in the list can cause conflicts in collections, as many things happen in background, like reallocating buffers or copying elements when adding new items. You have to take care of both. For lists targeting .NET 4 you could use the System.Collections.Concurrent namespace.

Thread-safe ListT property

If you are targetting .Net 4 there are a few options in System.Collections.Concurrent Namespace

You could use ConcurrentBag<T> in this case instead of List<T>



Related Topics



Leave a reply



Submit