Коллекция была изменена; операция перечисления может не выполняться - PullRequest
806 голосов
/ 03 марта 2009

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

Это сервер 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);
        }
    }
}

Ответы [ 13 ]

0 голосов
/ 13 декабря 2018

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

для вас ссылка на оригинальную ссылку: - https://bensonxion.wordpress.com/2012/05/07/serializing-an-ienumerable-produces-collection-was-modified-enumeration-operation-may-not-execute/

Когда мы используем классы .Net Serialization для сериализации объекта, где его определение содержит тип Enumerable, т.е. collection, вы легко получите InvalidOperationException, сообщив, что «Коллекция была изменена; Операция перечисления может не выполняться ", если кодирование выполняется в многопоточных сценариях. Основная причина в том, что классы сериализации будут перебирать коллекцию через перечислитель, как таковой, проблема заключается в попытке перебрать коллекцию при ее изменении.

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

Ну, .Net 4.0, который делает работу с многопоточными сценариями удобной. я обнаружил, что для этой проблемы с сериализацией в поле Collection можно воспользоваться классом ConcurrentQueue (Check MSDN), который является потокобезопасным и FIFO-коллекцией и делает код без блокировки.

Используя этот класс, в его простоте то, что вам нужно изменить для своего кода, заменяет тип Collection на него, используйте Enqueue, чтобы добавить элемент в конец ConcurrentQueue, удалите код блокировки. Или, если сценарий, над которым вы работаете, требует таких сборщиков, как List, вам потребуется еще немного кода, чтобы адаптировать ConcurrentQueue к вашим полям.

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

0 голосов
/ 04 октября 2013

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

0 голосов
/ 29 мая 2013

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

...