Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Using the Concurrent Dictionary - Thread Safe Collection Modification

Tags:

c#

concurrency

Recently I was running into the following exception when using a generic dictionary

An InvalidOperationException has occurred. A collection was modified

I realized that this error was primarily because of thread safety issues on the static dictionary I was using.

A little background: I currently have an application which has 3 different methods that are related to this issue.

  1. Method A iterates through the dictionary using foreach and returns a value.
  2. Method B adds data to the dictionary.
  3. Method C changes the value of the key in the dictionary.

Sometimes while iterating through the dictionary, data is also being added, which is the cause of this issue. I keep getting this exception in the foreach part of my code where I iterate over the contents of the dictionary. In order to resolve this issue, I replaced the generic dictionary with the ConcurrentDictionary and here are the details of what I did.

Aim : My main objective is to completely remove the exception

For method B (which adds a new key to the dictionary) I replaced .Add with TryAdd

For method C (which updates the value of the dictionary) I did not make any changes. A rough sketch of the code is as follows :

  static public int ChangeContent(int para)
  {
      foreach (KeyValuePair<string, CustObject> pair in static_container)
      {
             if (pair.Value.propA != para ) //Pending cancel 
             {
                pair.Value.data_id = prim_id;    //I am updating the content
                return 0;

             }
      }
     return -2;
  }

For method A - I am simply iterating over the dictionary and this is where the running code stops (in debug mode) and Visual Studio informs me that this is where the error occured.The code I am using is similar to the following

    static public CustObject RetrieveOrderDetails(int para)
    {
            foreach (KeyValuePair<string, CustObject> pair in static_container)
            {                   
                if (pair.Value.cust_id.Equals(symbol))
                {
                    if (pair.Value.OrderStatus != para) 
                    {
                       return pair.Value; //Found
                    }
                }
            }
            return null; //Not found
    }

Are these changes going to resolve the exception that I am getting.

Edit:

It states on this page that the method GetEnumerator allows you to traverse through the elements in parallel with writes (although it may be outdated). Isnt that the same as using foreach ?

like image 961
MistyD Avatar asked Feb 20 '13 05:02

MistyD


2 Answers

For modification of elements, one option is to manually iterate the dictionary using a for loop, e.g.:

Dictionary<string, string> test = new Dictionary<string, string>();
int dictionaryLength = test.Count();

for (int i = 0; i < dictionaryLength; i++)
{
    test[test.ElementAt(i).Key] = "Some new content";
}

Be weary though, that if you're also adding to the Dictionary, you must increment dictionaryLength (or decrement it if you move elements) appropriately.

Depending on what exactly you're doing, and if order matters, you may wish to use a SortedDictionary instead.

You could extend this by updating dictionaryLength explicitly by recalling test.Count() at each iteration, and also use an additional list containing a list of keys you've already modified and so on and so forth if there's a danger of missing any, it really depends what you're doing as much as anything and what your needs are.

You can further get a list of keys using test.Keys.ToList(), that option would work as follows:

Dictionary<string, string> test = new Dictionary<string, string>();
List<string> keys = test.Keys.ToList();
foreach (string key in keys)
{
    test[key] = "Some new content";
}

IEnumerable<string> newKeys = test.Keys.ToList().Except(keys);

if(newKeys.Count() > 0)
    // Do it again or whatever.

Note that I've also shown an example of how to find out whether any new keys were added between you getting the initial list of keys, and completing iteration such that you could then loop round and handle the new keys.

Hopefully one of these options will suit (or you may even want to mix and match- for loop on the keys for example updating that as you go instead of the length) - as I say, it's as much about what precisely you're trying to do as much as anything.

like image 71
Xefan Avatar answered Sep 29 '22 12:09

Xefan


Before doing foreach() try out copying container to a new instance

var unboundContainer = static_container.ToList();
foreach (KeyValuePair<string, CustObject> pair in unboundContainer)

Also I think updating Value property is not right from thread safety perspectives, refactor your code to use TryUpdate() instead.

like image 22
sll Avatar answered Sep 29 '22 13:09

sll