udidu
udidu

Reputation: 8588

Working with global Dictionary inside threads

Suppose that I have a Dictionary<string, string>. The dictionary is declared as public static in my console program.

If I'm working with threads and I want to do foreach on this Dictionary from one thread but at the same time another thread want to add item to the dictionary. This would cause a bug here because we can't modify our Dictionary while we are running on it with a foreach loop in another thread.

To bypass this problem I created a lock statement on the same static object on each operation on the dictionary.

Is this the best way to bypass this problem? My Dictionary can be very big and I can have many threads that want to foreach on it. As it is currently, things can be very slow.

Upvotes: 1

Views: 700

Answers (4)

Jon Hanna
Jon Hanna

Reputation: 113222

The big question is: Do you need the foreach to be a snapshot?

If the answer is "no", then use a ConcurrentDictionary and you will probably be fine. (The one remaining question is whether the nature of your inserts and reads hit the striped locks in a bad way, but if that was the case you'd be finding normal reads and writes to the dictionary even worse).

However, because it's GetEnumerator doesn't provide a snapshot, it will not be enumerating the same start at the beginning as it is at the end. It could miss items, or duplicate items. The question is whether that's a disaster to you or not.

If it would be a disaster if you had duplicates, but not otherwise, then you can filter out duplicates with Distinct() (whether keyed on the keys or both the key and value, as required).

If you really need it to be a hard snapshot, then take the following approach.

Have a ConcurrentDictionary (dict) and a ReaderWriterLockSlim (rwls). On both reads and writes obtain a reader lock (yes even though you're writing):

public static void AddToDict(string key, string value)
{
  rwls.EnterReadLock();
  try
  {
    dict[key] = value;
  }
  finally
  {
    rwls.ExitReadLock();
  }
}
public static bool ReadFromDict(string key, out string value)
{
  rwls.EnterReadLock();
  try
  {
    return dict.TryGetValue(key, out value);
  }
  finally
  {
    rwls.ExitReadLock();
  }
}

Now, when we want to enumerate the dictionary, we acquire the write lock (even though we're reading):

public IEnumerable<KeyValuePair<string, string>> EnumerateDict()
{
  rwls.EnterWriteLock();
  try
  {
    return dict.ToList();
  }
  finally
  {
    rwls.ExitWriteLock();
  }
}

This way we obtain the shared lock for reading and writing, because ConcurrentDictionary deals with the conflicts involved in that for us. We obtain the exclusive lock for enumerating, but just for long enough to obtain a snapshot of the dictionary in a list, which is then used only in that thread and not shared with any other.

Upvotes: 1

dthorpe
dthorpe

Reputation: 36072

Yes, you will have a problem updating the global dictionary while an enumeration is running in another thread.

Solutions:

  1. Require all users of the dictionary to acquire a mutex lock before accessing the object, and release the lock afterwards.
  2. Use .NET 4.0's ConcurrentDictionary class.

Upvotes: 0

Wyatt Barnett
Wyatt Barnett

Reputation: 15673

With .NET 4 you get a fancy new ConcurrentDictionary. I think there are some .NET 3.5-based implementations floating around.

Upvotes: 0

Thomas Levesque
Thomas Levesque

Reputation: 292345

Try using a ConcurrentDictionary<TKey, TValue>, which is designed for this kind of scenario.

There's a nice tutorial here on how to use it.

Upvotes: 7

Related Questions