It’s important to note that an enumerator does not have exclusive, thread-safe access to its collection. Even when a collection is synchronized, other threads can still modify the collection. Therefore, a collection’s contents can change while enumerating through it, which will cause the enumerator to throw an exception. So there are three key ways to safely enumerate a collection:
1. Lock the Collection During Enumeration
To prevent changes to a collection while enumerating through it, you can lock the collection using its SyncRoot property. Note that you can grow your own SyncRoot for collections that don’t have a SyncRoot property. The downside to this approach is that large collections may be locked for long periods of time, resulting in poor performance or deadlocks.
ArrayList list = new ArrayList(); lock (list.SyncRoot) { foreach (object obj in list) { // do something } }
2. Export Collection Contents to an Array
The best way to guarantee thread-safety is to exclusively own the collection, and the easiest way to do this is to export the collection’s contents to an array. The downside to this approach is the time and memory used to create a separate array.
ArrayList list = new ArrayList(); Array array = list.ToArray(); foreach (object obj in array) { // do something }
3. Catch Enumerator Exceptions
The final approach is simply to catch any exceptions and re-start the enumeration. The downside to this approach is the enumeration could restart multiple times for oft-changing collections, and there may be cases where you do not want to repeat the enumeration.
ArrayList list = new ArrayList(); bool finished = false; while (!finished) { try { foreach (object obj in array) { // do something } finished = true; } catch (InvalidOperationException) { // collection changed } }
Is the “ToArray” method thread safe?
Excellent question. If the array is synchronized, then yes, ToArray is thread-safe, as shown in Reflector:
public override object[] ToArray()
{
lock (this._root)
{
return this._list.ToArray();
}
}
But if not using a synchronized ArrayList, then you might want to explicitly lock the SyncRoot, similar to approach #1.