1047 votes

Collection a été modifiée ; opération d’énumération ne peut pas exécuter

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

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

Lorsqu'un Windows Forms client s'abonne, l'ID de l'abonné est ajouté à tous les abonnés de dictionnaire, et lorsque le client se désinscrit, il est supprimé à partir du dictionnaire. L'erreur se produit lors de la (ou après) une résiliation par le client. Il semble que la prochaine fois que l'NotifySubscribers() la méthode est appelée, la boucle foreach() en boucle échoue avec l'erreur dans la ligne objet. La méthode écrit l'erreur dans le journal des applications comme le montre le code ci-dessous. Lorsqu'un débogueur est attaché et une résiliation par le client, le code s'exécute bien.

Voyez-vous un problème avec ce code? Ai-je besoin de faire 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);
        }
    }
}

1858voto

JaredPar Points 333733

Ce qui se passe probablement est que procédant est indirectement changer le dictionnaire abonnés sous le capot pendant la boucle et menant à ce message. Vous pouvez vérifier cela en changeant

À

Si j’ai raison, le problème va disparaître

123voto

Mitch Wheat Points 169614

Lorsqu’un abonné annule l’abonnement vous modifiez du contenu de la collection d’abonnés au cours de l’énumération.

Il y a plusieurs façons de résoudre ce problème, l’un étant changeant la boucle for pour :

74voto

x4000 Points 1061

D'une façon plus efficace, à mon avis, est d'avoir une autre liste que vous déclarez que vous avez mis tout ce qui est "d'être supprimé". Puis, après vous terminé votre boucle principale (sans le .ToList()), vous en faire une autre boucle sur le "pour être supprimé de la liste, retrait de chaque entrée, comme il arrive. Donc, dans votre classe, vous ajoutez:

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

Puis vous modifiez:

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 permettra non seulement de résoudre votre problème, il vous empêchera de continuer à créer une liste à partir de votre dictionnaire, ce qui est cher si il y a beaucoup d'abonnés. En supposant que la liste des abonnés à retirer sur une itération donnée est inférieur au nombre total de personnes dans la liste, cela devrait être plus rapide. Mais, bien sûr, se sentir libre de profil, afin d'être certain que c'est le cas si il n'y a aucun doute dans votre utilisation spécifique à la situation.

47voto

Mohammad Sepahvand Points 5377

Vous pouvez également verrouiller votre dictionnaire d’abonnés pour l’empêcher d’être modifié chaque fois que son être en boucle :

5voto

luc.rg.roy Points 11

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

Ce que vous devez vraiment faire, c’est à partir de la fin et au début. Même si vous supprimez des éléments dans la liste, vous serez en mesure de continuer à lire.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X