Kolekcja została zmodyfikowana; operacja wyliczania może nie zostać wykonana

Nie mogę dojść do sedna tego błędu, ponieważ kiedy debugger jest dołączony, wydaje się, że nie występuje. Poniżej znajduje się kod.

Jest to serwer WCF w usłudze Windows. Metoda NotifySubscribers jest wywoływana przez serwis za każdym razem, gdy występuje zdarzenie danych (w losowych odstępach czasu, ale nie bardzo często - około 800 razy dziennie).

Gdy klient Windows Forms subskrybuje, identyfikator subskrybenta jest dodawany do słownika subskrybentów, a gdy klient rezygnuje z subskrypcji, jest usunięte ze słownika. Błąd występuje, gdy (lub po) Klient anuluje subskrypcję. Wygląda na to, że przy następnym wywołaniu metody NotifySubscribers() pętla foreach() zawiedzie z błędem w wierszu tematu. Metoda zapisuje błąd do dziennika aplikacji, jak pokazano w poniższym kodzie. Gdy dołączony jest debugger i klient wypisuje się z subskrypcji, kod wykonuje się dobrze.

Widzisz problem z tym kodem? Czy muszę zabezpieczyć słownik?
[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);
        }
    }
}
Author: cdonner, 2009-03-03

12 answers

Prawdopodobnie dzieje się tak, że SignalData pośrednio zmienia słownik subskrybentów pod maską podczas pętli i prowadzi do tej wiadomości. Możesz to sprawdzić zmieniając

foreach(Subscriber s in subscribers.Values)

Do

foreach(Subscriber s in subscribers.Values.ToList())

Jeśli mam rację, problem zniknie

 1312
Author: JaredPar,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-03-03 02:10:17

Kiedy subskrybent rezygnuje z subskrypcji, zmieniasz zawartość kolekcji subskrybentów podczas wyliczania.

Można to naprawić na kilka sposobów, jednym z nich jest zmiana pętli for na jawną .ToList():

public void NotifySubscribers(DataRecord sr)  
{
    foreach(Subscriber s in subscribers.Values.ToList())
    {
                                              ^^^^^^^^^  
        ...
 97
Author: Mitch Wheat,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-10-11 08:27:25

Bardziej efektywnym sposobem, moim zdaniem, jest posiadanie innej listy, na której deklarujesz, że umieszczasz wszystko, co jest "do usunięcia". Następnie po zakończeniu głównej pętli (bez .ToList ()), robisz kolejną pętlę nad listą "do usunięcia", usuwając każdy wpis tak jak to się dzieje. Więc w klasie dodajesz:

private List<Guid> toBeRemoved = new List<Guid>();

Potem zmieniasz na:

public void NotifySubscribers(DataRecord sr)
{
    toBeRemoved.Clear();

    ...your unchanged code skipped...

   foreach ( Guid clientId in toBeRemoved )
   {
        try
        {
            subscribers.Remove(clientId);
        }
        catch(Exception e)
        {
            System.Diagnostics.Debug.WriteLine("Unsubscribe Error " + 
                e.Message);
        }
   }
}

...your unchanged code skipped...

public void UnsubscribeEvent(Guid clientId)
{
    toBeRemoved.Add( clientId );
}

To nie tylko rozwiąże twój problem, ale uniemożliwi ci tworzenie listy ze słownika, która jest drogie, jeśli jest tam dużo abonentów. Zakładając, że lista subskrybentów do usunięcia na danej iteracji jest niższa niż całkowita liczba na liście, powinno to być szybsze. Ale oczywiście możesz go profilować, aby upewnić się, że tak jest, jeśli masz jakiekolwiek wątpliwości dotyczące konkretnej sytuacji użytkowania.

 54
Author: x4000,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-07-15 11:53:02

Możesz również zablokować słownik subskrybentów, aby zapobiec jego modyfikacjom, gdy jest zapętlony:

 lock (subscribers)
 {
         foreach (var subscriber in subscribers)
         {
               //do something
         }
 }
 33
Author: Mohammad Sepahvand,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-05-23 19:32:17

Uwaga: ogólnie Kolekcje.Net nie wspierają jednoczesnego wyliczania i modyfikowania. Jeśli spróbujesz zmodyfikować listę kolekcji, gdy będziesz w trakcie jej wyliczania, spowoduje to powstanie wyjątek.

Problem z tym błędem polega na tym, że nie możemy modyfikować listy / słownika podczas wykonywania pętli. Jeśli jednak iterujemy słownik używając tymczasowej listy jego kluczy, równolegle możemy modyfikować obiekt dictionary, ponieważ teraz nie iterujemy słownik(i iterację jego zbioru kluczy).

Próbka:

//get key collection from dictionary into a list to loop through
List<int> keys = new List<int>(Dictionary.Keys);

// iterating key collection using simple for-each loop
foreach (int key in keys)
{
  // Now we can perform any modification with values of dictionary.
  Dictionary[key] = Dictionary[key] - 1;
}

Oto wpis na blogu o tym rozwiązaniu.

I dla głębokiego nurkowania w stackoverflow: dlaczego występuje ten błąd?

 9
Author: open and free,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 11:55:12

Faktycznie wydaje mi się, że problem polega na tym, że usuwasz elementy z listy i oczekujesz, że nadal będziesz czytać listę tak, jakby nic się nie stało.

To, co naprawdę musisz zrobić, to zacząć od końca i z powrotem do początku. Nawet jeśli usuniesz elementy z listy, będziesz mógł ją dalej czytać.

 4
Author: luc.rg.roy,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-05-23 16:10:21

InvalidOperationException - Wystąpił błąd InvalidOperationException. Raportuje, że "kolekcja została zmodyfikowana" w pętli foreach-loop

Użyj instrukcji break po usunięciu obiektu.

Ex:

ArrayList list = new ArrayList(); 

foreach (var item in list)
{
    if(condition)
    {
        list.remove(item);
        break;
    }
}
 3
Author: vivek,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-08-17 14:22:50

Miałem ten sam problem i został rozwiązany, gdy użyłem pętli for zamiast foreach.

// foreach (var item in itemsToBeLast)
for (int i = 0; i < itemsToBeLast.Count; i++)
{
    var matchingItem = itemsToBeLast.FirstOrDefault(item => item.Detach);

   if (matchingItem != null)
   {
      itemsToBeLast.Remove(matchingItem);
      continue;
   }
   allItems.Add(itemsToBeLast[i]);// (attachDetachItem);
}
 2
Author: Daniel Moreshet,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-02-07 12:47:00

Widziałem wiele opcji na to, ale dla mnie ten był najlepszy.

ListItemCollection collection = new ListItemCollection();
        foreach (ListItem item in ListBox1.Items)
        {
            if (item.Selected)
                collection.Add(item);
        }

Następnie po prostu przełącz zbiór.

Należy pamiętać, że ListItemCollection może zawierać duplikaty. Domyślnie nic nie stoi na przeszkodzie dodaniu duplikatów do kolekcji. Aby uniknąć duplikatów można to zrobić:

ListItemCollection collection = new ListItemCollection();
            foreach (ListItem item in ListBox1.Items)
            {
                if (item.Selected && !collection.Contains(item))
                    collection.Add(item);
            }
 2
Author: Mike,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-03-16 13:57:23

Dobra, więc to, co mi pomogło, to iteracja do tyłu. Próbowałem usunąć wpis z listy, ale iterował w górę i zepsuł pętlę, bo wpis już nie istnieje:

for (int x = myList.Count - 1; x > -1; x--)
                        {

                            myList.RemoveAt(x);

                        }
 1
Author: Mark Aven,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-07-22 14:40:25

Możesz skopiować subscribers dictionary object do tego samego typu tymczasowego obiektu słownika, a następnie iterację tymczasowego obiektu słownika za pomocą pętli foreach.

 0
Author: Rezoan,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-05-29 13:59:41

Więc innym sposobem rozwiązania tego problemu byłoby zamiast usuwania elementów utworzyć nowy słownik i dodać tylko elementy, których nie chcesz usunąć, a następnie zastąpić oryginalny słownik nowym. Nie sądzę, że jest to zbyt duży problem z wydajnością, ponieważ nie Zwiększa to liczby iteracji nad strukturą.

 0
Author: ford prefect,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-10-04 14:04:10