In the last couple of posts I talked about threading in C#, dangers and advantages. Now I am going to present collections that you can use in an environment where threads are heavy used. So what’s the issue that we’re trying to solve here? Assuming that you have a queue or a stack or whatever, two threads accessing it at the same time will result in an exceptio. Concurrent collections solve this problem but while they’re guaranteed to be thread safe, the code that is using them is not meaning that you can still have issues if you don’t design your code carefully. An example of such a poor design can be found below:
[code lang=”csharp”]
var _queue = new ConcurrentQueue<int>();
if (!_queue.Contains(5))
_queue.Enqueue(5);
[/code]
While the collection itself is thread safe, the entire code is not. Why? Assuming you have two threads one of them could be just after the if, and the other one testing the condition. Obviously they will both introduce a 5 in the queue and thus inducing an undesired behavior.
Now, all collections reside under System.Collections.Concurrent and they allow you to use concurrent stacks, queues, bags and dictionaries. Keep in mind that while they provide thread safety(assuming your code is clean) they have significant performance drawbacks, normal collections being faster, up to three times faster than the safe ones.
There is another danger that you may not realize immediately. Two different threads can action upon the same collection, while one is iterating through the other one can write. While in normal collection this would throw an exception, this doesn’t happen with concurrent collections and the result is nothing but gibberish.
They are designed to perform well under heavy threading usage and thus sacrificing some performance and memory efficiency. Their underlying implementation relies on linked lists which are much less likely to have problems due to the fact that inserting/deleting elements is a just a matter of updating a couple of references compared to traditional lists where hundreds of elements are to be moved around.
As opposed to traditional collections, these provide special methods TryAdd/TryTake exposed by IProducerConsumerCollection<T> interface which is implemented by all concurrent collections except for the dictionary. These methods are testing if the operation is possible and if so it’s performed but this process happens in an atomic way removing the need of locking this statement.
Collections provided by System.Collections.Concurrent:
- ConcurrentStack<T>
- ConcurrentQueue<T>
- ConcurrentBag<T> (none)
- BlockingCollection<T> (none)
- ConcurrentDictionary<TKey,TValue>
Out of those, ConcurrentBag and BlockingCollection do not have non-concurrent equivalents, for the others I am sure you can guess them and because of this I will not dwell on them as their usage is rather rare. Below you’ll find brief usages of the others.
ConcurrentStack<T>
[code lang=”csharp”]
// All operations are atomically executed!
int value = 0;
var _stack = new ConcurrentStack<int>();
_stack.Push(value); //pushes 0
_stack.PushRange(new []{1,2,3,4}); // pushes an array of elements. stack: {0,1,2,3,4}
_stack.TryPeek(out value); // sees top of the stack: 4
_stack.TryPop(out value); // removes the last element. stack: {0,1,2,3}
_stack.TryPopRange(new[] {2, 3}); // removes an array of elements. stack: {0,1}
_stack.TryPop(out value); // removes al ement. stack: {0}
Console.WriteLine(value); // prints out 1
[/code]
ConcurrentQueue<T>
[code lang=”csharp”]
var _queue = new ConcurrentQueue<int>();
_queue.Enqueue(value);
_queue.TryPeek(out value);
_queue.TryDequeue(out value);
[/code]
ConcurrentDictionary<T>
[code lang=”csharp”]
int key = 0;
int value = 99;
int newValue = 100;
int comparisonValue = 99;
var _dict = new ConcurrentDictionary<int, int>();
_dict.TryAdd(key, value); // adds a new pair to the dictionary
_dict.ContainsKey(key); // returns true
_dict.TryGetValue(key, out value); // gets the value, without removing it
_dict.TryRemove(key, out value); // gets and removes the value
_dict.TryUpdate(key, newValue, comparisonValue); // replaces the value with a newValue based on the comparison with the comparisonValue
[/code]
Another question that I’ve been asked a lot is around atomic operations in C# and I want to address it now with the following answer:
Reads and writes of the following data types are atomic: bool, char, byte, sbyte, short, ushort, uint, int, float, and reference types. In addition, reads and writes of enum types with an underlying type in the previous list are also atomic. Reads and writes of other types, including long, ulong, double, and decimal, as well as user-defined types, are not guaranteed to be atomic.