I have recently noticed that inside the collection objects contained in System.Collections.Concurrent namespace it is common to see Collection.TrySomeAction()
rather then Collection.SomeAction()
.
What is the cause of this? I assume it has something to do with locking?
So I am wondering under what conditions could an attempt to (for example) Dequeue an item from a stack, que开发者_高级运维ue, bag etc.. fail?
Collections in System.Collections.Concurrent
namespace are considered to be thread-safe, so it is possible to use them to write multi-threaded programs that share data between threads.
Before .NET 4, you had to provide your own synchronization mechanisms if multiple threads might be accessing a single shared collection. You had to lock the collection each time you modified its elements. You also might need to lock the collection each time you accessed (or enumerated) it. That’s for the simplest of multi-threaded scenarios. Some applications would create background threads that delivered results to a shared collection over time. Another thread would read and process those results. You needed to implement your own message passing scheme between threads to notify each other when new results were available, and when those new results had been consumed. The classes and interfaces in System.Collections.Concurrent
provide a consistent implementation for those and other common multi-threaded programming problems involving shared data across threads in lock-free way.
Try<something>
has semantics - try to do that action and return operation result. DoThat
semantics usually use exception thrown mechanics to indicate error which can be not efficient. As examples there they can return false,
- if you try add new element, you might already have it in
ConcurentDictionary
; - if you try to get element from collection, it might not exists there;
- if you try to update element there are can be more recent element already, so method ensures it updates only element which intended to update.
Try to read:
- Patterns for Parallel Programming: Understanding and Applying Parallel Patterns with the .NET Framework 4 - best to start;
- Parallel Programming in the .NET Framework;
- read articles on Concurrency
- Thread-safe Collections in .NET Framework 4 and Their Performance Characteristics
What do you mean with fail?
Take the following example:
var queue = new Queue<string>();
string temp = queue.Dequeue();
// do something with temp
The above code with throw an exception, since we try to dequeue from an empty queue. Now, if you use a ConcurrentQueue<T>
instead:
var queue = new ConcurrentQueue<string>();
string temp;
if (queue.TryDequeue(out temp))
{
// do something with temp
}
The above code will not throw an exception. The queue will still fail to dequeue an item, but the code will not fail in the way of throwing an exception. The real use for this becomes apparent in a multithreaded environment. Code for the non-concurrent Queue<T>
would typically look something like this:
lock (queueLock)
{
if (queue.Count > 0)
{
string temp = queue.Dequeue();
// do something with temp
}
}
In order to avoid race conditions, we need to use a lock to ensure that nothing happens with the queue in the time that passes from checking Count
do calling Dequeue
. With ConcurrentQueue<T>
, we don't really need to check Count
, but can instead call TryDequeue
.
If you examine the types found in the Systems.Collections.Concurrent
namespace you will find that many of them wrap two operations that are typically called sequentially, and that would traditionally require locking (Count
followed by Dequeue
in ConcurrentQueue<T>
, GetOrAdd
in ConcurrentDictionary<TKey, TValue>
replaces sequences of calling ContainsKey
, adding an item and getting it, and so on).
If there is nothing to be "dequeued", for example... This "Try-Pattern" is used commonly all across FCL and BCL elements. That has nothing to do with locking, concurrent collections are (or at least should be) mostly implemented without locks...
精彩评论