Frage

Ich kann nicht auf den Grund für diesen Fehler ist, denn wenn der debugger angehängt ist, scheint es nicht zu kommen.Unten ist der code.

Dies ist ein WCF-server in einem Windows-Dienst.Die Methode NotifySubscribers heißt der service, wenn es ist ein Daten-Ereignis (in unregelmäßigen Abständen, aber nicht sehr oft - etwa 800-mal pro Tag).

Wenn Sie eine Windows Forms-client abonniert hat, wird die Abonnenten-ID ist Hinzugefügt, um die Abonnenten-Wörterbuch, und wenn der client abmeldet, wird Sie gelöscht aus dem Wörterbuch.Der Fehler tritt auf, wenn (oder nachdem) ein client abmeldet.Es scheint, dass das nächste mal die NotifySubscribers () - Methode aufgerufen wird, wird die foreach () - Schleife schlägt fehl, mit dem Fehler in der Betreff-Zeile.Die Methode schreibt die Fehler in die Anwendung anmelden, wie im code unten.Wenn ein debugger angeschlossen ist und ein client abmeldet, wird der code ausgeführt, in Ordnung.

Sehen Sie ein problem mit diesem code?Muss ich das Wörterbuch thread-sicher?

[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);
        }
    }
}
War es hilfreich?

Lösung

Was wahrscheinlich passiert, ist, dass SignalData indirekt die änderung der Abonnenten-Wörterbuch unter der Haube während der loop und die auf die Nachricht.Sie können dies überprüfen, indem Sie ändern

foreach(Subscriber s in subscribers.Values)

Zu

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

Wenn ich Recht habe, wird das problem verschwinden

Anrufen von Teilnehmern.Werte.ToList() kopiert die Werte von Abonnenten.Werte in eine separate Liste zu Beginn der foreach -.Nichts anderes hat Zugriff auf diese Liste (es ist nicht einmal eine variable name!), so kann nichts ändern es innerhalb der Schleife.

Andere Tipps

Wenn ein Teilnehmer das Abo kündigt Sie Inhalt der Sammlung von Abonnenten während der Enumeration ändern.

Es gibt mehr Möglichkeiten, dieses Problem zu beheben, eine der for-Schleife wird Ändern eine explizite .ToList() zu verwenden:

public void NotifySubscribers(DataRecord sr)  
{
    foreach(Subscriber s in subscribers.Values.ToList())
    {
                                              ^^^^^^^^^  
        ...

Eine effizientere Art und Weise, meiner Meinung nach, ist eine andere Liste haben, erklären Sie, dass Sie etwas setzen, die „entfernt werden“ in ist. Dann, nachdem Sie Ihre Hauptschleife beenden (ohne den .ToList ()), die Sie tun eine andere Schleife über die „entfernt werden“ -Liste, jeden Eintrag zu entfernen, wie es geschieht. So in Ihrer Klasse, die Sie hinzufügen:

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

Dann ändern Sie es an:

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 );
}

Dies wird nicht nur Ihr Problem lösen, wird es verhindern, dass Sie mit einer Liste aus dem Wörterbuch halten zu schaffen, was teuer ist, wenn es eine Menge von Abonnent in dort ist. Unter der Annahme, die Liste der Teilnehmer an einer bestimmten Iteration geringer ist als die Gesamtzahl in der Liste entfernt werden, sollte dies schneller sein. Aber natürlich fühlen sich frei, es zu profilieren sicher sein, dass der Fall ist, wenn es irgendein Zweifel in Ihrer spezifischen Nutzungssituation.

Sie können sperren auch Ihre Abonnenten es Wörterbuch zu verhindern, dass geändert, wenn sein Wesen geschlungen:

 lock (subscribers)
 {
         foreach (var subscriber in subscribers)
         {
               //do something
         }
 }

Warum diese Fehler?

Generell .Net Sammlungen unterstützen nicht zur gleichen Zeit aufgezählt und modifiziert werden. Wenn Sie versuchen, die Sammelliste während der Enumeration zu ändern, wirft es eine Ausnahme. Also das Problem hinter diesen Fehler ist, können wir die Liste / Wörterbuch nicht ändern, während wir durch die gleichen sind Looping.

Eine der Lösungen

Wenn wir iterieren ein Wörterbuch eine Liste der Tasten, parallel können wir das Dictionary-Objekt ändern, wie wir durch das Schlüssel Sammlung iterieren und nicht das Wörterbuch (und seine Schlüsselübergabe Iterieren).

Beispiel

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

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

Hier ist ein < em> Blogeintrag über diese Lösung.

Und für einen tiefen Tauchgang in Stackoverflow: Warum dieser Fehler auftritt

Eigentlich ist das Problem scheint mir, dass Sie Elemente aus der Liste entfernt und erwarten weiterhin die Liste zu lesen, als ob nichts geschehen wäre.

Was Sie wirklich brauchen, zu tun ist, von dem Ende beginnen und an den Anfang zurück. Auch wenn Sie Elemente aus der Liste entfernen können Sie es weiter zu lesen.

InvalidOperationException - Eine InvalidOperationException aufgetreten ist. Er berichtet eine in einer foreach-Schleife „Kollektion wurde geändert“

Mit break-Anweisung Sobald das Objekt entfernt wird.

ex:

ArrayList list = new ArrayList(); 

foreach (var item in list)
{
    if(condition)
    {
        list.remove(item);
        break;
    }
}

Ich hatte das gleiche Problem, und es wurde gelöst, als ich statt for eine foreach Schleife verwendet wird.

// 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);
}

Ich habe für diese vielen Möglichkeiten gesehen, aber mir dieses war das beste.

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

Dann einfach Schleife durch die Sammlung.

Beachten Sie, dass eine ListItemCollection Duplikate enthalten kann. Standardmäßig gibt es nichts zu verhindern Duplikate der Sammlung hinzugefügt werden. Um zu vermeiden, Duplikate Sie können dies tun:

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

Okay, so was hat mir geholfen, nach hinten wurde iteriert. Ich habe versucht, einen Eintrag aus einer Liste zu entfernen, aber Iterieren nach oben und schraubte die Schleife, weil der Eintrag nicht mehr vorhanden:

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

                            myList.RemoveAt(x);

                        }

Sie können Abonnenten Dictionary-Objekt zu einer gleichen Art temporären Wörterbuch Objekts kopieren und dann das temporäre Wörterbuch Objekt iterieren foreach-Schleife.

So ein anderer Weg, um dieses Problem zu lösen wäre, anstatt Entfernen der Elemente ein neues Wörterbuch erstellen und fügen Sie nur die Elemente, die Sie dann entfernen wollen nicht den Original-Wörterbuch mit dem neuen ersetzen. Ich glaube nicht, das ist zu viel ein Effizienzproblem ist, weil es nicht die Anzahl der Zeiten, die Sie über die Struktur iterieren nicht erhöht.

Es gibt einen Link, wo es sehr gut ausgearbeitet und Lösung ist auch gegeben. Probieren Sie es, wenn Sie die richtige Lösung bekam schreiben Sie bitte hier so verstehen können. Gegeben Lösung ist in Ordnung, dann wie die Post, so dass andere kann diese Lösung versuchen.

Sie Original-Link verweisen: - https://bensonxion.wordpress.com/2012/05/07/serializing-an-ienumerable-produces-collection-was-modified-enumeration-operation-may-not-execute/

Wenn wir .Net Serialisierung Klassen verwenden, um ein Objekt, in dem seine Definition enthält einen Enumerable Typen serialisiert werden, das heißt Sammlung, werden Sie leicht InvalidOperationException immer sagen, „Collection modifiziert wurde; Enumerationsvorgang ausführen kann nicht“, wo Ihre Codierung Multi-Thread-Szenarien unter. Die untere Ursache ist, dass die Serialisierung Klassen durch Sammlung über Enumerator iterieren werden, als solche, Problem geht zu versuchen, durch eine Sammlung zu durchlaufen, während es zu ändern.

Erste Lösung, wir können einfach Sperre als Synchronisationslösung verwenden, um sicherzustellen, der Vorgang zu dem Listenobjekt kann nur von einem Thread zu einem Zeitpunkt ausgeführt werden.  Offensichtlich werden Sie Leistungseinbuße erhalten, wenn Sie eine Sammlung dieses Objekts serialisiert werden wollen, dann für jeden von ihnen, wird die Sperre angewendet werden.

Nun, .NET 4.0, die der Umgang mit praktischen Multi-Threading-Szenarien machen. für diese Sammlung Feldproblem Serialisierung, fand ich, dass wir profitieren von ConcurrentQueue nur nehmen kann (Check MSDN) Klasse, das ist ein Thread-sicher und FIFO-Sammlung und macht Code Lock-frei.

diese Klasse verwendet, in seiner Einfachheit, das Zeug, was Sie für Ihren Code ändern ersetzen Sammlungstyp mit ihm, verwenden Enqueue ein Element zu dem Ende des ConcurrentQueue hinzufügen, entfernen jenen Sperrcode. Oder, wenn das Szenario auf dem Sie arbeiten tun Sammlung Sachen Liste benötigen, müssen Sie ein paar mehr Code ConcurrentQueue in die Felder anzupassen.

BTW, ConcurrentQueue doesnât eine Clear-Methode aufgrund der zugrunde liegenden Algorithmus, die atomar doesnât Genehmigung für die Sammlung zu löschen. so haben Sie es selbst zu tun, der schnellste Weg, um eine neue leere ConcurrentQueue für einen Ersatz neu zu erstellen.

Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top