La raccolta è stata modificata; l’operazione di enumerazione potrebbe non essere eseguita

Non riesco ad andare in fondo a questo errore, perché quando il debugger è collegato, non sembra che si verifichi. Di seguito è riportato il codice.

Questo è un server WCF in un servizio di Windows. Il metodo NotifySubscribers viene chiamato dal servizio ogni volta che c’è un evento di dati (a intervalli casuali, ma non molto spesso – circa 800 volte al giorno).

Quando un client Windows Form si iscrive, l’ID del sottoscrittore viene aggiunto al dizionario degli iscritti e quando il client si annulla, viene eliminato dal dizionario. L’errore si verifica quando (o dopo) un client si annulla. Sembra che al prossimo richiamo del metodo NotifySubscribers (), il ciclo foreach () fallisca con l’errore nella riga dell’object. Il metodo scrive l’errore nel registro dell’applicazione come mostrato nel codice sottostante. Quando un debugger è collegato e un client si annulla, il codice viene eseguito correttamente.

Vedi un problema con questo codice? Devo rendere il dizionario sicuro per i thread?

[ServiceBehavior(InstanceContextMode=InstanceContextMode.Single)] public class SubscriptionServer : ISubscriptionServer { private static IDictionary subscribers; public SubscriptionServer() { subscribers = new Dictionary(); } 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(); 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); } } } 

Quello che probabilmente accadrà è che SignalData sta cambiando indirettamente il dizionario degli abbonati sotto il cofano durante il ciclo e portando a quel messaggio. Puoi verificarlo cambiando

 foreach(Subscriber s in subscribers.Values) 

A

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

Se ho ragione, il problema scomparirà

Quando un sottoscrittore annulla la sottoscrizione, stai modificando i contenuti della raccolta di Sottoscrittori durante l’enumerazione.

Ci sono diversi modi per risolvere questo problema, uno sta cambiando il ciclo for per usare un .ToList() esplicito:

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

Un modo più efficiente, secondo me, è di avere un’altra lista in cui dichiari di aver inserito tutto ciò che è “da rimuovere”. Quindi, dopo aver terminato il ciclo principale (senza .ToList ()), fai un altro ciclo sull’elenco “da rimuovere”, rimuovendo ogni voce nel momento in cui accade. Quindi nella tua class aggiungi:

 private List toBeRemoved = new List(); 

Quindi lo cambi in:

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

Questo non solo risolverà il tuo problema, ma ti impedirà di continuare a creare una lista dal tuo dizionario, il che è costoso se ci sono molti iscritti lì dentro. Supponendo che l’elenco di abbonati da rimuovere su una determinata iterazione sia inferiore al numero totale nell’elenco, dovrebbe essere più veloce. Ma ovviamente sentitevi liberi di profilarlo per essere sicuri che sia il caso se c’è qualche dubbio nella vostra specifica situazione d’uso.

Puoi anche bloccare il dizionario dei tuoi iscritti per evitare che venga modificato ogni volta che viene eseguito il ciclo:

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

Nota : in generale, le collezioni .Net non supportano l’enumerazione e la modifica allo stesso tempo. Se si tenta di modificare l’elenco di raccolta mentre si è nel mezzo dell’enumerazione, verrà generata un’eccezione.

Quindi il problema dietro questo errore è, non possiamo modificare la lista / dizionario mentre stiamo eseguendo il ciclo. Ma se iteriamo un dizionario usando una lista temporanea delle sue chiavi, in parallelo possiamo modificare l’object dizionario, perché ora non stiamo iterando il dizionario (e iterando la sua collezione di chiavi).

campione:

 //get key collection from dictionary into a list to loop through List keys = new List(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; } 

Ecco un post sul blog su questa soluzione.

E per un’immersione profonda nello stackoverflow: perché si verifica questo errore?

In realtà il problema mi sembra che tu stia rimuovendo gli elementi dalla lista e mi aspetto di continuare a leggere la lista come se nulla fosse accaduto.

Quello che devi veramente fare è iniziare dalla fine e tornare all’inizio. Anche se rimuovi elementi dalla lista, sarai in grado di continuare a leggerlo.

InvalidOperationException- Si è verificata un’eccezione InvalidOperationException. Segnala una “collezione è stata modificata” in un ciclo foreach

Usa l’istruzione break, una volta rimosso l’object.

ex:

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

Ho avuto lo stesso problema, ed è stato risolto quando ho usato un ciclo for invece di 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); } 

Ho visto molte opzioni per questo, ma per me questo era il migliore.

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

Quindi semplicemente scorrere la collezione.

Essere consapevoli del fatto che un ListItemCollection può contenere duplicati. Di default non c’è nulla che impedisca l’aggiunta di duplicati alla collezione. Per evitare duplicati puoi farlo:

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

Okay, quindi quello che mi ha aiutato è stato andare all’indietro. Stavo cercando di rimuovere una voce da una lista ma iterando verso l’alto e ha rovinato il ciclo perché la voce non esisteva più:

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

È ansible copiare l’object dizionario degli abbonati su uno stesso tipo di object dizionario temporaneo e quindi iterare l’object dizionario temporaneo utilizzando il ciclo foreach.

Quindi un modo diverso per risolvere questo problema sarebbe invece di rimuovere gli elementi, creare un nuovo dizionario e aggiungere solo gli elementi che non volevi rimuovere, quindi sostituire il dizionario originale con quello nuovo. Non penso che questo sia un problema di efficienza eccessivo perché non aumenta il numero di volte che si itera sulla struttura.