Я не могу докопаться до этой ошибки, потому что, когда отладчик подключен, кажется, что это не происходит. Ниже приведен код.
Это сервер WCF в службе Windows. Метод NotifySubscribeers вызывается службой всякий раз, когда происходит событие данных (через случайные интервалы, но не очень часто - около 800 раз в день).
Когда клиент Windows Forms подписывается, идентификатор подписчика добавляется в словарь подписчиков, а когда клиент отписывается, он удаляется из словаря. Ошибка происходит, когда (или после) клиент отписывается. Похоже, что при следующем вызове метода NotifySubscribeers () цикл foreach () завершится с ошибкой в строке темы. Метод записывает ошибку в журнал приложения, как показано в коде ниже. Когда отладчик подключен и клиент отписывается, код выполняется нормально.
Вы видите проблему с этим кодом? Нужно ли сделать словарь потокобезопасным?
[ServiceBehavior(InstanceContextMode=InstanceContextMode.Single)]
public class SubscriptionServer : ISubscriptionServer
{
private static IDictionary<Guid, Subscriber> subscribers;
public SubscriptionServer()
{
subscribers = new Dictionary<Guid, Subscriber>();
}
public void NotifySubscribers(DataRecord sr)
{
foreach(Subscriber s in subscribers.Values)
{
try
{
s.Callback.SignalData(sr);
}
catch (Exception e)
{
DCS.WriteToApplicationLog(e.Message,
System.Diagnostics.EventLogEntryType.Error);
UnsubscribeEvent(s.ClientId);
}
}
}
public Guid SubscribeEvent(string clientDescription)
{
Subscriber subscriber = new Subscriber();
subscriber.Callback = OperationContext.Current.
GetCallbackChannel<IDCSCallback>();
subscribers.Add(subscriber.ClientId, subscriber);
return subscriber.ClientId;
}
public void UnsubscribeEvent(Guid clientId)
{
try
{
subscribers.Remove(clientId);
}
catch(Exception e)
{
System.Diagnostics.Debug.WriteLine("Unsubscribe Error " +
e.Message);
}
}
}
источник
Ответы:
Вероятно, происходит то, что SignalData косвенно изменяет словарь подписчиков под капотом во время цикла и приводит к этому сообщению. Вы можете проверить это, изменив
к
Если я прав, проблема исчезнет
Вызов
subscribers.Values.ToList()
копирует значенияsubscribers.Values
в отдельный список в началеforeach
. Ничто другое не имеет доступа к этому списку (у него даже нет имени переменной!), Поэтому ничто не может изменить его внутри цикла.источник
subscribers.Values
, что вforeach
цикле изменяются . Вызовsubscribers.Values.ToList()
копирует значенияsubscribers.Values
в отдельный список в началеforeach
. Ничто другое не имеет доступа к этому списку (у него даже нет имени переменной!) , Поэтому ничто не может изменить его внутри цикла.ToList
также может выдать, если коллекция была изменена во времяToList
выполнения.ToList
не атомная операция. Что еще смешнее, вToList
основном он сам выполняет свои функцииforeach
для копирования элементов в новый экземпляр списка, что означает, что вы устранилиforeach
проблему, добавив дополнительную (хотя и более быструю)foreach
итерацию.Когда подписчик отменяет подписку, вы меняете содержимое коллекции подписчиков во время перечисления.
Есть несколько способов исправить это, один из которых заключается в изменении цикла for для использования явного
.ToList()
:источник
Более эффективный способ, на мой взгляд, состоит в том, чтобы иметь другой список, в который вы заявляете, что вы помещаете в него все, что «должно быть удалено». Затем после завершения основного цикла (без .ToList ()) вы делаете еще один цикл над списком «подлежащих удалению», удаляя каждую запись, как это происходит. Итак, в вашем классе вы добавляете:
Затем вы измените его на:
Это не только решит вашу проблему, но и избавит вас от необходимости создавать список из вашего словаря, что дорого, если там много подписчиков. Предполагая, что список подписчиков, которые будут удалены на любой итерации, меньше, чем общее число в списке, это должно быть быстрее. Но, конечно, не стесняйтесь профилировать его, чтобы быть уверенным, что это так, если есть какие-либо сомнения в вашей конкретной ситуации использования.
источник
Вы также можете заблокировать словарь подписчиков, чтобы предотвратить его изменение при зацикливании:
источник
MarkerFrequencies
словарь внутри самой блокировки, а это означает, что исходный экземпляр больше не заблокирован. Также попробуйте использоватьfor
вместоforeach
, см. Это и это . Дайте мне знать, если это решит это.System.Collections.Concurrent
пространстве имен.Почему эта ошибка?
В целом коллекции .Net не поддерживают одновременное перечисление и изменение. Если вы попытаетесь изменить список коллекции во время перечисления, это вызовет исключение. Таким образом, проблема, стоящая за этой ошибкой, заключается в том, что мы не можем изменить список / словарь, пока мы повторяем его.
Одно из решений
Если мы выполняем итерацию по словарю, используя список его ключей, параллельно мы можем модифицировать объект словаря, поскольку мы выполняем итерацию по коллекции ключей, а не по словарю (и итерируем коллекцию ключей).
пример
Вот сообщение в блоге об этом решении.
И для глубокого погружения в StackOverflow: почему возникает эта ошибка?
источник
На самом деле мне кажется, что проблема заключается в том, что вы удаляете элементы из списка и ожидаете продолжить чтение списка, как будто ничего не произошло.
Что вам действительно нужно сделать, это начать с конца и вернуться к началу. Даже если вы удалите элементы из списка, вы сможете продолжить его чтение.
источник
InvalidOperationException - произошло InvalidOperationException. Он сообщает, что «коллекция была изменена» в цикле foreach
Используйте оператор break, как только объект будет удален.
например:
источник
У меня была та же проблема, и она была решена, когда я использовал
for
цикл вместоforeach
.источник
Итак, что помогло мне, итерация назад. Я пытался удалить запись из списка, но делал итерации вверх, и это запутало цикл, потому что запись больше не существует:
источник
Я видел много вариантов для этого, но для меня этот был лучшим.
Тогда просто переберите коллекцию.
Имейте в виду, что ListItemCollection может содержать дубликаты. По умолчанию ничто не препятствует добавлению дубликатов в коллекцию. Чтобы избежать дубликатов, вы можете сделать это:
источник
Принятый ответ неточен и неверен в худшем случае. Если изменения будут сделаны во время
ToList()
, вы все равно можете получить ошибку. Помимоlock
, какую производительность и безопасность потоков необходимо учитывать, если у вас есть открытый член, правильное решение может использовать неизменяемые типы .В общем случае неизменный тип означает, что вы не можете изменить его состояние после его создания. Итак, ваш код должен выглядеть так:
Это может быть особенно полезно, если у вас есть публичный член. Другие классы всегда могут
foreach
использовать неизменяемые типы, не беспокоясь об изменении коллекции.источник
Вот конкретный сценарий, который требует специализированного подхода:
Dictionary
Часто перечислены.Dictionary
Изменяется нечасто.В этом сценарии создание копии
Dictionary
(илиDictionary.Values
) перед каждым перечислением может быть довольно дорогостоящим. Моя идея решить эту проблему - повторно использовать одну и ту же кэшированную копию в нескольких перечислениях и просматриватьIEnumerator
оригинал наDictionary
предмет исключений. Перечислитель будет кеширован вместе с скопированными данными и опрошен перед началом нового перечисления. В случае исключения кэшированная копия будет отброшена, и будет создана новая. Вот моя реализация этой идеи:Пример использования:
К сожалению, эта идея в настоящее время не может использоваться с классом
Dictionary
в .NET Core 3.0, потому что этот класс не генерирует исключение измененной коллекции, когда перечисляются и методыRemove
иClear
вызываются. Все остальные контейнеры, которые я проверял, ведут себя последовательно. Я проверил систематически эти классы:List<T>
,Collection<T>
,ObservableCollection<T>
,HashSet<T>
,SortedSet<T>
,Dictionary<T,V>
иSortedDictionary<T,V>
. Только два вышеупомянутых методаDictionary
класса в .NET Core не лишают законной силы перечисление.Обновление: я исправил вышеуказанную проблему, сравнив также длину кэшированной и исходной коллекции. Это исправление предполагает, что словарь будет передан непосредственно в качестве аргумента
EnumerableSnapshot
конструктору, и его идентичность не будет скрыта (например, проекцией):dictionary.Select(e => e).ΤοEnumerableSnapshot()
.Важный: вышеупомянутый класс не потокобезопасен. Он предназначен для использования из кода, работающего исключительно в одном потоке.
источник
Вы можете скопировать объект словаря подписчиков во временный объект словаря того же типа, а затем выполнить итерацию объекта временного словаря, используя цикл foreach.
источник
Поэтому другим способом решения этой проблемы было бы вместо удаления элементов создать новый словарь и добавить только те элементы, которые вы не хотите удалять, а затем заменить исходный словарь новым. Я не думаю, что это слишком большая проблема эффективности, потому что это не увеличивает количество итераций по структуре.
источник
Есть одна ссылка, где она разработана очень хорошо, и решение также дается. Попробуйте, если у вас есть правильное решение, пожалуйста, напишите здесь, чтобы другие могли понять. Данное решение в порядке, тогда как пост, так что другие могут попробовать это решение.
для вас ссылка оригинальная ссылка: - https://bensonxion.wordpress.com/2012/05/07/serializing-an-ienumerable-produces-collection-was-modified-enumeration-operation-may-not-execute/
Когда мы используем классы .Net Serialization для сериализации объекта, где его определение содержит тип Enumerable, то есть коллекцию, вы легко получите InvalidOperationException, говорящее «Коллекция была изменена; операция перечисления может не выполняться», если кодирование выполняется в многопоточных сценариях. Основная причина в том, что классы сериализации будут перебирать коллекцию через перечислитель, поэтому проблема заключается в попытке перебрать коллекцию при ее изменении.
Первое решение, мы можем просто использовать блокировку в качестве решения для синхронизации, чтобы гарантировать, что операция с объектом List может выполняться только из одного потока за раз. Очевидно, вы получите снижение производительности, что если вы хотите сериализовать коллекцию этого объекта, то для каждого из них будет применена блокировка.
Ну, .Net 4.0, который делает работу с многопоточными сценариями удобной. для этой проблемы с сериализацией в поле «Коллекция» я обнаружил, что мы можем просто воспользоваться классом ConcurrentQueue (Check MSDN), который является поточно-ориентированным и FIFO-коллекцией и делает код свободным от блокировки.
Используя этот класс, в его простоте материал, который вам нужно изменить для своего кода, заменяет им тип Collection, используйте Enqueue, чтобы добавить элемент в конец ConcurrentQueue, удалите код блокировки. Или, если сценарий, над которым вы работаете, требует таких сборщиков, как List, вам потребуется еще немного кода для адаптации ConcurrentQueue к вашим полям.
Кстати, ConcurrentQueue не имеет метода Clear из-за базового алгоритма, который не допускает атомарной очистки коллекции. так что вы должны сделать это сами, самый быстрый способ - создать новое пустое ConcurrentQueue для замены.
источник
Я лично столкнулся с этим недавно в незнакомом коде, где он был в открытом dbcontext с .Remove (item) Линка. У меня было чертовски много времени на обнаружение ошибок, потому что элементы были удалены при первой итерации, и если я попытался сделать шаг назад и повторить шаг назад, коллекция оказалась пустой, поскольку я находился в том же контексте!
источник