que puede operación net modificada framework enumeración ejecute ejecutar colección coleccion c# wcf concurrency dictionary thread-safety

c# - net - La colección fue modificada; operación de enumeración no puede ejecutar



colección modificada puede que no se ejecute la operación de enumeración vb net (12)

No puedo llegar al final de este error, porque cuando el depurador está conectado, no parece que ocurra. A continuación se muestra el código.

Este es un servidor WCF en un servicio de Windows. El servicio llama al método NotifySubscribers siempre que hay un evento de datos (a intervalos aleatorios, pero no muy a menudo, aproximadamente 800 veces al día).

Cuando un cliente de Windows Forms se suscribe, el ID de suscriptor se agrega al diccionario de suscriptores, y cuando el cliente cancela la suscripción, se elimina del diccionario. El error ocurre cuando (o después) un cliente se da de baja. Parece que la próxima vez que se llame al método NotifySubscribers (), el bucle foreach () falla con el error en la línea de asunto. El método escribe el error en el registro de la aplicación como se muestra en el código a continuación. Cuando se adjunta un depurador y el cliente se da de baja, el código se ejecuta correctamente.

¿Ves algún problema con este código? ¿Necesito hacer que el diccionario sea seguro para los hilos?

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


Bueno, lo que me ayudó fue iterando hacia atrás. Estaba tratando de eliminar una entrada de una lista, pero estaba iterando hacia arriba y se complicó el bucle porque la entrada ya no existía:

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


Cuando un suscriptor se da de baja, está cambiando los contenidos de la colección de suscriptores durante la enumeración.

Hay varias formas de solucionar esto, una de ellas es cambiar el bucle for para usar un .ToList() explícito:

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


En realidad, el problema me parece que está eliminando elementos de la lista y que espera continuar leyendo la lista como si nada hubiera pasado.

Lo que realmente necesitas hacer es comenzar desde el final y volver al principio. Incluso si eliminas elementos de la lista, podrás seguir leyendo.


He visto muchas opciones para esto, pero para mí esta fue la mejor.

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

Luego simplemente recorre la colección.

Tenga en cuenta que una colección ListItem puede contener duplicados. Por defecto, no hay nada que impida que se agreguen duplicados a la colección. Para evitar duplicados puedes hacer esto:

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


Lo que probablemente suceda es que SignalData está cambiando indirectamente el diccionario de suscriptores bajo el capó durante el ciclo y lleva a ese mensaje. Puedes verificar esto cambiando

foreach(Subscriber s in subscribers.Values)

A

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

Si estoy en lo cierto, el problema desaparecerá.

Llamar a suscriptores. Valores.Lista () copia los valores de los suscriptores.Valores en una lista separada al inicio de foreach. Nada más tiene acceso a esta lista (¡ni siquiera tiene un nombre de variable!), Así que nada puede modificarla dentro del bucle.


Por lo tanto, una forma diferente de resolver este problema sería, en lugar de eliminar los elementos, crear un nuevo diccionario y solo agregar los elementos que no desea eliminar, luego reemplazar el diccionario original por el nuevo. No creo que esto sea demasiado problema de eficiencia porque no aumenta el número de veces que se repite sobre la estructura.


Puede copiar el objeto de diccionario de los suscriptores a un mismo tipo de objeto de diccionario temporal y luego iterar el objeto de diccionario temporal utilizando el bucle foreach.


También puede bloquear el diccionario de sus suscriptores para evitar que se modifique cada vez que se realiza un bucle:

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


Tuve el mismo problema, y ​​se resolvió cuando usé un bucle for lugar de 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); }


Una manera más eficiente, en mi opinión, es tener otra lista en la que declare que ha puesto cualquier cosa que se "elimine". Luego, después de que finalice su bucle principal (sin la .ToList ()), realice otro bucle sobre la lista "para eliminar", eliminando cada entrada a medida que sucede. Así que en tu clase agregas:

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

Luego lo cambias a:

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

Esto no solo solucionará su problema, sino que también evitará que tenga que seguir creando una lista de su diccionario, lo cual es costoso si hay muchos suscriptores allí. Suponiendo que la lista de suscriptores que se eliminará en cualquier iteración dada sea inferior al número total en la lista, esto debería ser más rápido. Pero, por supuesto, siéntase libre de crear un perfil para asegurarse de que ese sea el caso si hay alguna duda en su situación específica de uso.


InvalidOperationException : se ha producido una InvalidOperationException. Informa que una "colección fue modificada" en un bucle foreach

Utilice la instrucción break, una vez que el objeto es eliminado.

ex:

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


Nota : En general, las colecciones .Net no admiten la enumeración y modificación al mismo tiempo. Si intenta modificar la lista de recopilación mientras está en medio de enumerarla, se generará una excepción.

Entonces, el problema detrás de este error es que no podemos modificar la lista / diccionario mientras estamos realizando el ciclo. Pero si iteramos un diccionario usando una lista temporal de sus claves, en paralelo podemos modificar el objeto del diccionario, porque ahora no estamos iterando el diccionario (y repitiendo su colección de claves).

muestra:

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

Aquí hay una entrada de blog sobre esta solución.

Y para una inmersión profunda en : ¿Por qué se produce este error?