Коллекция была изменена; операция перечисления может не выполняться. Блокировка используется везде, насколько это возможно?

Это небольшая программа, которую я пишу и использую только.

Теперь я собираюсь написать код всех областей, где я использую hashset, вызвавший эту проблему.

Я не понимаю, как это возможно. Этот элемент используется только в MainWindow

hsProxyList - это hashset

  HashSet<string> hsProxyList = new HashSet<string>();

ошибка произошла при следующей итерации

 lock (hsProxyList)
            {
    int irRandomProxyNumber = GenerateRandomValue.GenerateRandomValueMin(hsProxyList.Count, 0);
    int irLocalCounter = 0;
    foreach (var vrProxy in hsProxyList)
    {
       if (irLocalCounter == irRandomProxyNumber)
       {
       srSelectedProxy = vrProxy;
       break;
       }
         irLocalCounter++;
       }
    }
}

В других местах, где я использую hsProxyList

Я не блокирую объект, когда получаю его счет - я полагаю, это не вызовет какой-либо ошибки, но может быть неправильной - не смертельно важно

 lblProxyCount.Content = "remaining proxy count: " + hsProxyList.Count;

новый

lock (hsProxyList)
{
    hsProxyList.Remove(srSelectedProxy);
}

новый

lock (hsProxyList)
{
    hsProxyList = new HashSet<string>();
    foreach (var vrLine in File.ReadLines(cmbBoxSelectProxy.SelectedItem.ToString()))
    {
        hsProxyList.Add(vrLine);
    }
}

Как видно, я использую блокировку всюду. Это многопоточное программное обеспечение. Все hsProxyList используются в MainWindow.xaml.cs - это приложение WPF для С#

Ответ 1

Проблема заключается в том, что у вас

lock (hsProxyList)
{
    hsProxyList = new HashSet<string>();
    // etc
}

Все блокировки находятся на определенном объекте, однако вы меняете объект, когда вы выполняете hsProxyList = new HashSet<string>();, поэтому объект, к которому относится переменная hsProxyList, больше не заблокирован.

Ответ 2

Здесь есть две проблемы. Первое, что уже было указано, заключается в том, что вы блокируете хэш-набор, одновременно меняя объект hsProxyList на:

lock (hsProxyList)
{
    hsProxyList = new HashSet<string>();
    // hsProxyList is no longer locked.
}

Вторая (и более тонкая) проблема заключается в том, что вы предполагаете, что Count не требует блокировки. Это не безопасное предположение. Во-первых, вы не знаете, как HashSet его реализовала. Тот факт, что Count является операцией O(1), указывает, что существует переменная-член, которая отслеживает счет. Это означает, что на Add или Remove эта переменная должна быть обновлена. Реализация Add может выглядеть примерно так:

bool Add( T item ) {
    this.count++;
    // Point A.
    addItemToHashSet(item);
}

Обратите внимание, что переменная Count увеличивается, а затем добавляется элемент. Если поток, вызывающий Add, перехвачен в точке A, и выполняется ваш другой поток, который вызывает Count, вы получите счетчик, который больше, чем число фактических элементов (Count был incremented, но addItemToHashSet не имеет).

Это может не иметь серьезных последствий, но если вы повторяете элементы Count, это может привести к сбою. Подобное поведение также возможно при вызове Remove.