When calling DoStuffToDictionary(dictionaryTwo), is it safe to assume the operations within the method body including indexers, and LINQ extension methods will also be thread safe?
To phrase this question differently, could a cross thread exception or deadlock arise?
var dictionaryOne = new ConcurrentDictionary<int,int>();
var dictionaryTwo = new Dictionary<int,int>();
DoStuffToDictionary(dictionaryOne);
DoStuffToDictionary(dictionaryTwo);
void DoStuffToDictionary(IDictionary<int,int> items) {
// Manipulate dictionary
if (items[0] == -1) {
items[0] = 0; // Dumb example, but are indexers like this OK?
}
}
There are several problems with this code:
IDictionaryinterface can be implemented by any type of a dictionaryYour example is certainly not thread safe, since you are working on an
IDictionary<int,int>interface, which doesn’t guarantee any thread safety. Even your code passes both aDictionaryand aConcurrentDictionaryto the method.Transactions need to be atomic to make them thread-safe
Even if the dictionary implementation was guaranteed to be thread safe, your code wouldn’t be because you are not locking the access to your dictionary between two calls:
Returning a LINQ query is never thread-safe
If you are using LINQ to return
IEnumerableorIQueriablefrom your method, then locks have little effect, unless you use theToList()method to evaluate the expression immediatelly and cache results. This is due to the fact that a LINQ only “prepares” the query for execution. If you are returning anIEnumerablefrom a method, actual dictionary will be accessed after your method ends (and therefore, outside the lock).The biggest problem with this code lies in the fact that you are passing the
IDictionaryinstance around, which means that other parts of your code can access it directly, and must lock on the same lock object instance very carefully. This is painful, error-prone to implement correctly, easy to break by accident, and hard to detect (race conditions may show symptoms on rare occasions).You can do several things to improve the code:
Don’t pass the
IDictionaryaround, but instead your own interface (preferred)Make the dictionary a private member of a class which implements some custom interface, abstract all operations, and use a lock to ensure thread safety (or use a
ConcurrentDictionaryunder the hood). This way you are sure that all calls are being locked using the same lock instance.Don’t use the interface, but rather always pass the
ConcurrentDictionaryThis will be thread safe as long as you use specific atomic methods which a
ConcurrentDictionaryprovides (GetOrAdd,AddOrUpdate, etc.). Using simple access methods like you did in your example won’t be thread safe, which means you still need to be careful with it. Additional downside is that you won’t be able to abstract the functionality if you ever need to (it will be impossible to wrap/proxy the dictionary, and you won’t be able to use other dictionary implementations).Pass the
IDictionaryaround, and lock on the dictionary itself (not recommended at all).This is an ugly hack, which is unfortunately used more often than it should be. It means you need to do this in every part of your app which accesses this dictionary, taking additional care to lock multiple operations along the way.