Pregunta

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

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

Cuando un cliente de Windows Forms se suscribe, la ID del 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 un problema con este código? ¿Debo hacer que el diccionario sea seguro para el hilo?

[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);
        }
    }
}
¿Fue útil?

Solución

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. Puede verificar esto cambiando

foreach(Subscriber s in subscribers.Values)

Para

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

Si estoy en lo cierto, el problema desaparecerá

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

Otros consejos

Cuando un suscriptor se da de baja, está cambiando el contenido 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())
    {
                                              ^^^^^^^^^  
        ...

Una forma más eficiente, en mi opinión, es tener otra lista en la que declares que pones todo lo que se va a "quitar". dentro. Luego, después de finalizar su ciclo principal (sin .ToList ()), realiza otro ciclo sobre el " para ser eliminado " lista, eliminando cada entrada a medida que sucede. Entonces 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 resolverá su problema, sino que evitará que tenga que seguir creando una lista a partir 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.

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

¿Por qué este error?

En general, las colecciones .Net no admiten la enumeración y modificación al mismo tiempo. Si intenta modificar la lista de recopilación durante la enumeración, se genera una excepción. Entonces, el problema detrás de este error es que no podemos modificar la lista / diccionario mientras hacemos el mismo bucle.

Una de las soluciones

Si iteramos un diccionario usando una lista de sus claves, en paralelo podemos modificar el objeto del diccionario, mientras iteramos a través de la colección de claves y no el diccionario (y la iteración de su colección de claves).

Ejemplo

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

Aquí hay un < em> blog post sobre esta solución.

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

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

Lo que realmente necesita hacer es comenzar desde el final y volver al principio. Incluso si elimina elementos de la lista, podrá continuar leyéndolo.

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 se elimine el objeto.

ex:

ArrayList list = new ArrayList(); 

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

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

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 ListItemCollection puede contener duplicados. Por defecto, no hay nada que impida que se agreguen duplicados a la colección. Para evitar duplicados, puede hacer esto:

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

Bien, entonces lo que me ayudó fue iterar hacia atrás. Estaba tratando de eliminar una entrada de una lista, pero la iteración hacia arriba y se arruinó el bucle porque la entrada ya no existía:

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

                            myList.RemoveAt(x);

                        }

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.

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 en la estructura.

Hay un enlace donde se elaboró ??muy bien & amp; También se da solución. Pruébelo si tiene la solución adecuada, publique aquí para que otros puedan entenderlo. La solución dada está bien, entonces al igual que la publicación para que otros puedan probar esta solución.

para su referencia enlace original: - https://bensonxion.wordpress.com/2012/05/07/serializing-an-ienumerable-produces-collection-was-modified-enumeration-operation-may-not-execute/

Cuando usamos clases de serialización .Net para serializar un objeto donde su definición contiene un tipo Enumerable, es decir, colección, obtendrás fácilmente la InvalidOperationException diciendo que se modificó Collection; la operación de enumeración no se puede ejecutar " donde su codificación se encuentra en escenarios multi-hilo. La causa principal es que las clases de serialización se repetirán a través de la recopilación a través del enumerador, como tal, el problema va a tratar de iterar a través de una colección mientras se modifica.

Primera solución, simplemente podemos usar el bloqueo como una solución de sincronización para asegurar que La operación del objeto Lista solo se puede ejecutar desde un hilo a la vez.  Obviamente, obtendrá una penalización de rendimiento que Si desea serializar una colección de ese objeto, entonces para cada uno de ellos, se aplicará el bloqueo.

Bueno, .Net 4.0, que hace que manejar situaciones de subprocesos múltiples sea útil. para este problema de campo de la colección de serialización, encontré que podemos beneficiarnos de la clase ConcurrentQueue (Check MSDN), que es una colección FIFO segura para subprocesos y hace que el código esté libre de bloqueo.

Usando esta clase, en su simplicidad, las cosas que necesita modificar para su código están reemplazando el tipo de Colección con él, use Enqueue para agregar un elemento al final de ConcurrentQueue, elimine esos códigos de bloqueo. O, si el escenario en el que está trabajando requiere elementos de recopilación como Lista, necesitará un poco más de código para adaptar ConcurrentQueue en sus campos.

Por cierto, ConcurrentQueue no tiene un método Clear debido al algoritmo subyacente que no permite la eliminación atómica de la colección. así que tienes que hacerlo tú mismo, la forma más rápida es volver a crear una nueva ConcurrentQueue vacía para un reemplazo.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top