Reputation: 8588
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
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
Reputation: 36072
Yes, you will have a problem updating the global dictionary while an enumeration is running in another thread.
Solutions:
Upvotes: 0
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
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