.NET 4 contains rich set of tools, that allow to create parallelized code more easy. But when we start processing chunk of data in parallel threads, we need to synchronize these threads, and we need some storage for the results of work. Now exist a big number of methods, solving sync issues, and we can realize in code any of them. But MS Parallel Extensions team already do it, and .NET 4 beta versions include set of thread-safe data structures. They implement some popular types of collections, and I want to do an overview of them.
1. Queue: ConcurrentQueue<T>
This is a classic FIFO queue, which can be safely accessed from few threads simultaneously. API of this collection is very simple:
ConcurrentQueue<int> queue = new ConcurrentQueue<int>();
queue.Enqueue(10);
int t;
Console.WriteLine(queue.TryPeek(out t));
Console.WriteLine(queue.TryDequeue(out t));
TryPeek() and TryDequeue() methods return an element from queue, but TryPeek() doesn’t remove it from queue. Both methods returns
true, if they take an element, else they return
false. You can add an element by calling Enqueue() method. Count of elements in the queue you can get by Count property, and if collection contains at least 1 element, property IsEmpty returns true.
2. Stack: ConcurrentStack<T>
This is a simple LIFO stack, that can be used in the concurrent environment. In contrast to queue, you can easily add or take the range of elements:
ConcurrentStack<int> stack = new ConcurrentStack<int>();
stack.Push(10);
stack.PushRange(new int[] { 1, 2, 3, 4, 5 });
int t;
if (stack.TryPop(out t))
{
Console.WriteLine("Pop: " + t);
}
if (stack.TryPeek(out t))
{
Console.WriteLine("Peek: " + t);
}
int[] ts = new int[5];
int count;
if ((count = stack.TryPopRange(ts, 0, 3)) > 0)
{
Console.WriteLine("PopRange");
for (int i = 0; i < count; i++)
{
Console.WriteLine(ts[i]);
}
}
Here is the result of work of this code:
TryPeek() and TryPop() methods return bool values, and TryPopRange() – count of retrieved elements. You may add range of values by calling PushRange() method.
3. Collection: ConcurrentBag<T>
This is an unordered collection of data, which is looks like a set, but it can store duplicate items. It doesn’t guarantee any order of elements. I think, it’s a most simple data structure:
ConcurrentBag<int> bag = new ConcurrentBag<int>(new int[] { 1, 1, 2, 3 });
bag.Add(70);
int t;
bag.TryPeek(out t);
Console.WriteLine(t);
bag.Add(110);
Console.WriteLine();
for (int i = 0; i < 3; i++)
{
bag.TryTake(out t);
Console.WriteLine(t);
}
This piece of code produce following console output:
Looking at screenshot, you can notice, that elements retrieved in LIFO. I repeat: no any orders is guaranteed.
Take a look at the constructor of bag – you can set initial range of elements, and constructors of queue and stack have appropriate overloads too.
4. Dictionary: ConcurrentDictionary<TKey, TValue>
It’s just a variant of Dictionary<TKey, TValue>, which can be accessed concurrently. API for this collection has some thread-safe specific features:
ConcurrentDictionary<string, string> dict = new ConcurrentDictionary<string, string>();
dict.TryAdd("name", "OFC340");
dict.TryAdd("age", "25");
dict.TryAdd("age", "25");
Create/update/delete actions can be executed by calling one of Try* methods, which return true on success, else false. In the code sample, first attempt to add value “25” with key “age” is ok, and TryAdd return true. Second attempt is fail, and TryAdd() return false, and exception will not be generated. Other example:
string t = string.Empty;
Console.WriteLine(dict.TryGetValue("nokey", out t));
Key “nokey” is not present in the dictionary, but TryGetValue() method doesn’t generate any exception, and only return false. You can remove an element as shown here:
Console.WriteLine(dict.TryRemove("age", out t));
Using properties Values and Keys you can receive actual on the moment of call collections of values and keys of dictionary.
So, you can see, that these data structures is very basic, and don’t contains tons of functions – only necessary things. I like this concept – it’s a base structures and too many functions is redundant. If you need something else, you can easily add it at any time.
In addition to described data structures, .NET 4 beta 1 contains list ConcurrentLinkedList<T>. But I don’t cover it, because MSDN contains following warning: «ConcurrentLinkedList(of T) is planned to be removed prior to the final release of Visual Studio 2010. Please do not use this class». And in the .NET 4 beta 2 this collection is removed.
Ok, I show you 4 basic thread-safe collections of data. In the next part of the article I show you BlockingCollection<T> – more complex and interest thread-safe storage. Stay tuned! :)