[C#] Exception: Collection was modified

natae·2022년 11월 29일
0

Csharp

목록 보기
9/9

개요

  • 주요 자료구조로 사용되는 List, Dictionary에 대해, 어느 상황에서 Collection was modified가 발생하는지 확인
  • 그리고 어떻게 해결할 수 있는지 확인

코드

public void CollectionModification()
{
    var dictionary = new Dictionary<int, int>() { { 0, 1 }, { 1, 1 }, { 2, 1 } };
    DictionaryAdd(dictionary);
    DictionaryRemove(dictionary);
    DictionaryUpdate(dictionary);

    var list = new List<int>() {0, 1, 2};
    ListAdd(list);    
    ListRemove(list);
}

public void DictionaryAdd(Dictionary<int, int> dictionary)
{
    try
    {
        foreach (var kvp in dictionary)
        {
            dictionary.Add(3, 1);
        }
    }
    catch (Exception e)
    {
        Console.WriteLine($"Message: {e.Message}, DictionaryAdd");
    }
}

public void DictionaryRemove(Dictionary<int, int> dictionary)
{
    try
    {
        foreach (var kvp in dictionary)
        {
            dictionary.Remove(0);
        }
    }
    catch (Exception e)
    {
        Console.WriteLine($"Message: {e.Message}, DictionaryRemove");
    }
}

public void DictionaryUpdate(Dictionary<int, int> dictionary)
{
    try
    {
        foreach (var kvp in dictionary)
        {
            dictionary[1] = 3;
        }
    }
    catch (Exception e)
    {
        Console.WriteLine($"Message: {e.Message}, DictionaryUpdate");
    }
}

public void ListAdd(List<int> list)
{
    try
    {
        foreach (var item in list)
        {
            list.Add(1);
        }
    }
    catch (Exception e)
    {
        Console.WriteLine($"Message: {e.Message}, ListAdd");
    }
}

public void ListRemove(List<int> list)
{
    try
    {
        foreach (var item in list)
        {
            list.Remove(1);
        }
    }
    catch (Exception e)
    {
        Console.WriteLine($"Message: {e.Message}, ListRemove");
    }
}

출력

Message: Collection was modified; enumeration operation may not execute., DictionaryAdd
Message: Collection was modified; enumeration operation may not execute., ListAdd
Message: Collection was modified; enumeration operation may not execute., ListRemove
  • Dictionary의 경우 Add, List의 경우 Add, Remove에서 발생

해결?

  • Dictionary.Add, List.Add에 대해 Concurrent 자료구조를 사용
  • Exception은 발생하지 않지만, Count가 계속 증가하기 때문에 무한루프 발생
    • for 루프를 사용해도 무한루프는 동일
public void DictionaryAddSafe(Dictionary<int, int> dictionary)
{
    try
    {
    	var temp = 3;
        var concurrentDictionary = new ConcurrentDictionary<int, int>(dictionary);
        foreach (var kvp in concurrentDictionary)
        {
            concurrentDictionary.TryAdd(temp, 1); // 무한 루프
            temp++;
        }
    }
    catch (Exception e)
    {
        Console.WriteLine($"Message: {e.Message}, DictionaryAddSafe");
    }
}
public void ListAddSafe(List<int> list)
{
    try
    {
        var concurrentBag = new ConcurrentBag<int>(list);
        foreach (var item in concurrentBag)
        {
            list.Add(1); // 무한 루프
        }
    }
    catch (Exception e)
    {
        Console.WriteLine($"Message: {e.Message}, ListAddSafe");
    }
}

해결

  • List.Remove의 경우 for 루프로 변경
  • 역순으로 순회해서 index가 밀리지 않음
public void ListRemoveSafe(List<int> list)
{
    try
    {
        for (var i = list.Count-1; i >= 0 ; i--)
        {
            list.Remove(1);
        }
    }
    catch (Exception e)
    {
        Console.WriteLine($"Message: {e.Message}, ListRemoveSafe");
    }
}

멀티쓰레드 환경이라면?

  • lock 또는 Concurrent 자료구조 사용
public void DictionaryAddMutiThreadSafe1(Dictionary<int, int> dictionary)
{
    Task.Run(() =>
    {
        Thread.Sleep(500);
        lock (dictionary)
        {
            dictionary.Add(3, 1);
        }


    });

    lock (dictionary)
    {
        foreach (var kvp in dictionary)
        {
            Thread.Sleep(1000);
        }
    }
}

public void DictionaryAddMutiThreadSafe2(Dictionary<int, int> dictionary)
{
    var concurrentDictionary = new ConcurrentDictionary<int, int>(dictionary);

    Task.Run(() =>
    {
        Thread.Sleep(500);
        concurrentDictionary.TryAdd(3, 1);
    });

    foreach (var kvp in concurrentDictionary)
    {
        Thread.Sleep(1000);
    }
}

결론

  • Add의 경우 Concurrent 자료구조로 Exception은 막을 수 있지만, 무한루프가 발생하기 때문에 for, foreach 내부에서는 Add를 안하는게 바람직해 보임
profile
서버 프로그래머

0개의 댓글