Handling Concurrent Modifications in C# Collections

Introduction

When working with collections in C#, modifying a collection while iterating over it can lead to runtime exceptions. This is particularly relevant when dealing with scenarios where elements are added or removed from a collection concurrently during iteration, such as event notification systems using callbacks.

The error message "Collection was modified; enumeration operation may not execute" indicates that an attempt has been made to modify the collection while it is being enumerated. Understanding how to handle this situation is crucial for developing robust applications.

The Problem

Consider a Windows Communication Foundation (WCF) server managing subscribers who receive notifications via callbacks. A dictionary stores subscriber information, with operations allowing clients to subscribe or unsubscribe. An error may occur when modifications are made to the collection while it’s being iterated through—specifically during notification dispatching after some clients unsubscribe.

Why This Error Occurs

.NET collections (e.g., Dictionary<TKey, TValue>) do not allow modification while iterating over them. Iteration relies on an internal state that becomes invalid when modifications are made, resulting in the exception mentioned above.

Solutions to Handle Concurrent Modifications

Several approaches can address this issue effectively:

1. Create a Copy of the Collection for Enumeration

One method is to iterate over a snapshot of the collection rather than the original. This can be achieved using ToList() or ToArray() on the collection being iterated, ensuring that modifications do not affect ongoing enumeration.

public void NotifySubscribers(DataRecord sr)
{
    foreach (Subscriber s in subscribers.Values.ToList())
    {
        try
        {
            s.Callback.SignalData(sr);
        }
        catch (Exception e)
        {
            DCS.WriteToApplicationLog(e.Message, 
              System.Diagnostics.EventLogEntryType.Error);

            UnsubscribeEvent(s.ClientId);
        }
    }
}

2. Maintain a Separate List for Modifications

Another efficient approach is to use an auxiliary list that tracks items marked for removal or addition. After the main iteration completes, iterate over this auxiliary list to make modifications.

private List<Guid> toBeRemoved = new List<Guid>();

public void NotifySubscribers(DataRecord sr)
{
    toBeRemoved.Clear();

    foreach (Subscriber s in subscribers.Values)
    {
        try
        {
            s.Callback.SignalData(sr);
        }
        catch (Exception e)
        {
            DCS.WriteToApplicationLog(e.Message, 
              System.Diagnostics.EventLogEntryType.Error);

            toBeRemoved.Add(s.ClientId);
        }
    }

    foreach (Guid clientId in toBeRemoved)
    {
        try
        {
            subscribers.Remove(clientId);
        }
        catch(Exception e)
        {
            System.Diagnostics.Debug.WriteLine("Unsubscribe Error " + 
                e.Message);
        }
    }
}

public void UnsubscribeEvent(Guid clientId)
{
    toBeRemoved.Add(clientId);
}

3. Iterate Backwards

For lists, iterating backwards allows safe removal of elements since it does not affect the indices of yet-to-be-visited items.

for (int x = myList.Count - 1; x >= 0; x--)
{
    myList.RemoveAt(x);
}

This technique avoids invalidating the loop counter as elements are removed.

Best Practices

  • Thread Safety: Consider using thread-safe collections like ConcurrentDictionary if your application is multi-threaded.
  • Consistent State: Ensure that operations on collections maintain a consistent state to prevent data corruption or unexpected behavior.
  • Profiling and Testing: Test and profile different solutions in real-world scenarios, as efficiency may vary based on the size of the collection and frequency of modifications.

By understanding these patterns and techniques, developers can efficiently manage concurrent modifications in C# collections, enhancing application reliability and performance.

Leave a Reply

Your email address will not be published. Required fields are marked *