Коллекция была изменена, операция перечисления не может выполняться

У меня есть приложение multithreads, и я получаю эту ошибку

************** Exception Text **************
System.InvalidOperationException: Collection was modified; enumeration operation may not execute.
   at System.ThrowHelper.ThrowInvalidOperationException(ExceptionResource resource)
   at System.Collections.Generic.List`1.Enumerator.MoveNextRare()
   at System.Collections.Generic.List`1.Enumerator.MoveNext()
   ...

У меня, вероятно, проблема с моей коллекцией, потому что в одном потоке я читаю свою коллекцию, а в другом потоке меня модифицирую коллекцию.

public readonly ObservableCollectionThreadSafe<GMapMarker> Markers = new ObservableCollectionThreadSafe<GMapMarker>();


public void problem()
{
  foreach (GMapMarker m in Markers)
  {
    ...
  }
}

Я пытаюсь заблокировать сборку с помощью этого кода, но не работает.

public void problem()
    {
       lock(Markers)
       {
         foreach (GMapMarker m in Markers)
         {
           ...
         }
       }
    }

Любые идеи по устранению этой проблемы?

Ответ 1

Это довольно распространенная ошибка - изменение коллекции во время ее итерации с использованием foreach, имейте в виду, что foreach использует экземпляр IEnumerator.

Попробуйте перебрать коллекцию с помощью for() с дополнительной проверкой индекса, чтобы, если индекс вышел за границы, вы могли применить дополнительную логику для его обработки. Вы также можете использовать LINQ Count() качестве другого условия выхода из цикла, оценивая значение Count каждый раз, если базовое перечисление не реализует ICollection:

Если Markers реализуют IColletion - заблокируйте SyncRoot:

lock (Markers.SyncRoot)

Использовать for():

for (int index = 0; index < Markers.Count(); index++)
{
    if (Markers>= Markers.Count())
    {
       // TODO: handle this case to avoid run time exception
    }
}

Этот пост может оказаться полезным: как работают циклы foreach в С#?

Ответ 2

Вам нужно зафиксировать как на стороне чтения, так и на стороне записи. В противном случае один из потоков не будет знать о блокировке и попытается прочитать/изменить коллекцию, в то время как другой изменяет/читает (соответственно) с заблокированной задержкой

Ответ 3

Попробуйте прочитать клон своей коллекции

foreach (GMapMarker m in Markers.Copy())
{
   ...
}

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

Итак, я думаю, будет лучше, если вы заблокировали сбор при чтении и записи процессов.

Ответ 4

Вы можете использовать foreach, но вам нужно бросить коллекцию в список и использовать оператор точки для доступа к методам поведения.

Пример: Markers.Tolist(). ForEach (i = > i.DeleteObject())

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

Ответ 5

Я бы предложил использовать AsyncCommand, потому что AsyncCommand либо взят, либо нет, а при использовании lock(Markers) разрешает повторное размещение. (см. https://github.com/StephenCleary/AsyncEx/wiki/AsyncLock):

   private readonly AsyncLock _markersMutex = new AsyncLock();

   using (await _markersMutex.LockAsync().ConfigureAwait(false))
   {
     foreach (GMapMarker m in Markers)
     {
       ...
     }
   }

Кроме того, AsyncLock позволяет заменить Thread.Sleep своим асинхронным эквивалентом, await Task.Delay(TimeSpan.FromSeconds(1))

Ответ 6

Я решил эту проблему с помощью

var data = getData();
lock(data)
{
    return getData().Select(x => new DisplayValueModel(x));
}

вместо

return getData().Select(x => new DisplayValueModel(x));