Question

Je ne peux pas aller au fond de cette erreur, car lorsque le débogueur est connecté, cela ne semble pas se produire. Ci-dessous le code.

Il s’agit d’un serveur WCF dans un service Windows. La méthode NotifySubscribers est appelée par le service chaque fois qu'il y a un événement de données (à des intervalles aléatoires, mais pas très souvent - environ 800 fois par jour).

Lorsqu'un client Windows Forms est abonné, son ID est ajouté au dictionnaire des abonnés et, lorsque le client se désabonne, il est supprimé du dictionnaire. L'erreur se produit lorsqu'un client se désabonne (ou après). Il semble que la prochaine fois que la méthode NotifySubscribers () soit appelée, la boucle foreach () échoue avec l'erreur dans la ligne de sujet. La méthode écrit l'erreur dans le journal de l'application, comme indiqué dans le code ci-dessous. Lorsqu'un débogueur est attaché et qu'un client se désabonne, le code s'exécute correctement.

Voyez-vous un problème avec ce code? Dois-je rendre le dictionnaire thread-safe?

[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);
        }
    }
}
Était-ce utile?

La solution

Ce qui est susceptible de se produire, c’est que SignalData modifie indirectement le dictionnaire des abonnés sous le capot pendant la boucle et entraîne ce message. Vous pouvez le vérifier en modifiant

foreach(Subscriber s in subscribers.Values)

Pour

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

Si j'ai raison, le problème disparaîtra

L'appel de subscribers.Values.ToList () copie les valeurs de subscribers.Values ??dans une liste distincte au début de foreach. Rien d'autre n'a accès à cette liste (elle n'a même pas de nom de variable!), Donc rien ne peut la modifier dans la boucle.

Autres conseils

Lorsqu'un abonné se désabonne, vous modifiez le contenu de la collection d'abonnés lors de l'énumération.

Il existe plusieurs façons de résoudre ce problème, l'une étant de changer la boucle for pour utiliser un .ToList () explicite:

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

Selon moi, un moyen plus efficace consiste à disposer d'une autre liste indiquant que vous avez mis tout ce qui est "à supprimer". dans. Ensuite, après avoir terminé votre boucle principale (sans le .ToList ()), vous effectuez une autre boucle sur le "à supprimer". liste, en supprimant chaque entrée au fur et à mesure. Donc, dans votre classe, vous ajoutez:

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

Ensuite, vous le changez en:

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

Cela résoudra non seulement votre problème, mais vous évitera de devoir continuer à créer une liste à partir de votre dictionnaire, ce qui coûte cher s'il y a beaucoup d'abonnés. En supposant que la liste des abonnés à supprimer sur une itération donnée soit inférieure au nombre total dans la liste, cela devrait être plus rapide. Mais bien sûr, n'hésitez pas à le décrire pour vous assurer que tel est le cas en cas de doute sur votre situation d'utilisation spécifique.

Vous pouvez également verrouiller le dictionnaire de vos abonnés pour l'empêcher d'être modifié à chaque boucle:

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

Pourquoi cette erreur?

En général, les collections .Net ne peuvent pas être énumérées et modifiées en même temps. Si vous essayez de modifier la liste de collections lors de l'énumération, une exception est levée. Le problème derrière cette erreur est donc que nous ne pouvons pas modifier la liste / le dictionnaire tant que nous parcourons la même chose.

Une des solutions

Si nous itérons un dictionnaire en utilisant une liste de ses clés, nous pouvons parallèlement modifier l'objet dictionnaire, car nous itérons à travers la collection de clés et pas le dictionnaire (et itérant sa collection de clés).

Exemple

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

Voici un < em> blog sur cette solution.

Et pour une plongée approfondie dans StackOverflow: Pourquoi cette erreur se produit-elle?

En fait, le problème me semble que vous supprimez des éléments de la liste et que vous vous attendez à continuer de lire la liste comme si de rien n'était.

Ce que vous devez vraiment faire est de commencer par la fin et de revenir au début. Même si vous supprimez des éléments de la liste, vous pourrez continuer à la lire.

InvalidOperationException - Une exception InvalidOperationException s'est produite. Il signale qu'une " collection a été modifiée " dans une boucle foreach

Utilisez l'instruction break, une fois que l'objet est supprimé.

ex:

ArrayList list = new ArrayList(); 

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

J'ai eu le même problème, et il a été résolu lorsque j'ai utilisé une boucle pour au lieu 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);
}

J'ai vu beaucoup d'options pour cela mais pour moi celui-ci était le meilleur.

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

Ensuite, parcourez simplement la collection.

Sachez qu'un ListItemCollection peut contenir des doublons. Par défaut, rien n'empêche l'ajout de doublons à la collection. Pour éviter les doublons, procédez comme suit:

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

D'accord, alors ce qui m'a aidé, c'est de revenir en arrière. J'essayais de supprimer une entrée d'une liste, mais en effectuant une itération vers le haut, la boucle a été bousillée parce que l'entrée n'existait plus:

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

                            myList.RemoveAt(x);

                        }

Vous pouvez copier l’objet dictionnaire des abonnés dans un même type d’objet dictionnaire temporaire, puis itérer l’objet dictionnaire temporaire à l’aide de la boucle foreach.

Donc, une autre façon de résoudre ce problème serait, au lieu de supprimer les éléments, de créer un nouveau dictionnaire et d’ajouter les éléments que vous ne vouliez pas supprimer, puis de remplacer le dictionnaire original par le nouveau. Je ne pense pas que ce soit un problème d'efficacité, car cela n'augmente pas le nombre d'itérations sur la structure.

Il y a un lien où il s'est très bien élaboré & amp; la solution est également donnée. Essayez-le si vous avez la solution appropriée, postez ici pour que les autres puissent comprendre. La solution donnée est ok, alors comme l’affiche pour que les autres puissent essayer ces solutions.

pour vous référencez le lien d'origine: - https://bensonxion.wordpress.com/2012/05/07/serializing-an-ienumerable-produces-collection-was-modified-enumeration-operation-may-not-execute/

Lorsque nous utilisons les classes de sérialisation .Net pour sérialiser un objet dont la définition contient un type Enumerable, c.-à-d. vous obtiendrez facilement InvalidOperationException en disant "La collection a été modifiée; l'opération d'énumération ne peut pas être exécutée " où votre codage est dans des scénarios multi-thread. La cause inférieure est que les classes de sérialisation vont parcourir la collection via un énumérateur, en tant que telles, Le problème est d'essayer de parcourir une collection en la modifiant.

Première solution, nous pouvons simplement utiliser lock comme solution de synchronisation pour nous assurer que l'opération sur l'objet List ne peut être exécutée qu'à partir d'un thread à la fois.  De toute évidence, vous aurez une pénalité de performance qui si vous souhaitez sérialiser une collection de cet objet, le verrou sera appliqué à chacun d'eux.

Eh bien, .Net 4.0, qui facilite le traitement des scénarios multi-threading. pour ce problème de champ de sérialisation Collection, j’ai trouvé que nous pouvions simplement tirer parti de la classe ConcurrentQueue (Check MSDN), qui est une collection FIFO thread-safe et rend le code sans verrouillage.

En utilisant cette classe, dans sa simplicité, ce que vous devez modifier pour votre code remplace le type Collection par ce dernier. utilisez Enqueue pour ajouter un élément à la fin de ConcurrentQueue, supprimez ce code de verrouillage. Ou, si le scénario sur lequel vous travaillez nécessite des éléments de collecte tels que List, vous aurez besoin de quelques codes supplémentaires pour adapter ConcurrentQueue à vos champs.

BTW, ConcurrentQueue n’a pas de méthode Clear en raison de l’algorithme sous-jacent qui ne permet pas d’effacer de manière atomique la collection. vous devez donc le faire vous-même. Le moyen le plus rapide est de recréer un nouveau ConcurrentQueue vide pour le remplacer.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top