Je ne peux pas aller au fond de cette erreur, car lorsque le débogueur est connecté, il ne semble pas se produire. Voici 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 (à intervalles aléatoires, mais pas très souvent - environ 800 fois par jour).
Lorsqu'un client Windows Forms s'abonne, l'ID d'abonné est ajouté au dictionnaire des abonnés et lorsque le client se désabonne, il est supprimé du dictionnaire. L'erreur se produit lorsque (ou après) un client se désabonne. Il semble que la prochaine fois que la méthode NotifySubscribers () est appelée, la boucle foreach () échoue avec l'erreur dans la ligne d'objet. La méthode écrit l'erreur dans le journal des applications 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 sûr pour les threads?
[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);
}
}
}
la source
Réponses:
Ce qui se passe probablement, c'est que SignalData modifie indirectement le dictionnaire des abonnés sous le capot pendant la boucle et conduit à ce message. Vous pouvez le vérifier en modifiant
À
Si j'ai raison, le problème disparaîtra
L'appel
subscribers.Values.ToList()
copie les valeurs desubscribers.Values
dans une liste distincte au début deforeach
. 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 à l'intérieur de la boucle.la source
subscribers.Values
est en cours de modification à l'intérieur de laforeach
boucle. L'appelsubscribers.Values.ToList()
copie les valeurs desubscribers.Values
dans une liste distincte au début deforeach
. 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 à l'intérieur de la boucle.ToList
peut également se produire si la collection a été modifiée pendant l'ToList
exécution.ToList
n'est pas une opération atomique. Ce qui est encore plus drôle,ToList
basiquement, fait son propreforeach
interne pour copier les éléments dans une nouvelle instance de liste, ce qui signifie que vous avez résolu unforeach
problème en ajoutant uneforeach
itération supplémentaire (bien que plus rapide) .Lorsqu'un abonné se désabonne, vous modifiez le contenu de la collection d'abonnés pendant l'énumération.
Il existe plusieurs façons de résoudre ce problème, l'une étant de modifier la boucle for pour utiliser une explicite
.ToList()
:la source
Un moyen plus efficace, à mon avis, est d'avoir une autre liste dans laquelle vous déclarez que vous mettez tout ce qui est "à supprimer". Ensuite, après avoir terminé votre boucle principale (sans le .ToList ()), vous effectuez une autre boucle sur la liste «à supprimer», supprimant chaque entrée au fur et à mesure. Donc, dans votre classe, vous ajoutez:
Ensuite, vous le changez en:
Cela résoudra non seulement votre problème, mais vous évitera d'avoir à créer une liste à partir de votre dictionnaire, ce qui est coûteux s'il y a beaucoup d'abonnés. En supposant que la liste des abonnés à supprimer sur une itération donnée est inférieure au nombre total dans la liste, cela devrait être plus rapide. Mais bien sûr, n'hésitez pas à le profiler pour être sûr que c'est le cas s'il y a un doute dans votre situation d'utilisation spécifique.
la source
Vous pouvez également verrouiller le dictionnaire de vos abonnés pour l'empêcher d'être modifié chaque fois qu'il est mis en boucle:
la source
MarkerFrequencies
dictionnaire à l'intérieur du verrou lui-même, ce qui signifie que l'instance d'origine n'est plus verrouillée. Essayez également d'utiliser unfor
au lieu d'unforeach
, voir ceci et cela . Faites-moi savoir si cela résout le problème.System.Collections.Concurrent
espace de noms.Pourquoi cette erreur?
En général, les collections .Net ne prennent pas en charge l'énumération et la modification en même temps. Si vous essayez de modifier la liste de collection pendant l'énumération, elle déclenche une exception. Donc, le problème derrière cette erreur est que nous ne pouvons pas modifier la liste / dictionnaire pendant que nous parcourons la même chose.
Une des solutions
Si nous itérons un dictionnaire en utilisant une liste de ses clés, en parallèle, nous pouvons modifier l'objet dictionnaire, car nous parcourons la collection de clés et non le dictionnaire (et itérons sa collection de clés).
Exemple
Voici un article de blog sur cette solution.
Et pour une plongée profonde dans StackOverflow: Pourquoi cette erreur se produit-elle?
la source
En fait, le problème me semble que vous supprimez des éléments de la liste et que vous vous attendez à continuer à lire la liste comme si de rien n'était.
Ce que vous devez vraiment faire, c'est commencer par la fin et revenir au début. Même si vous supprimez des éléments de la liste, vous pourrez continuer à la lire.
la source
InvalidOperationException - Une exception InvalidOperationException s'est produite. Il signale une "collection a été modifiée" dans une boucle foreach
Utilisez l'instruction break, une fois l'objet supprimé.
ex:
la source
J'ai eu le même problème, et il a été résolu lorsque j'ai utilisé une
for
boucle à la place deforeach
.la source
D'accord, ce qui m'a aidé à répéter. J'essayais de supprimer une entrée d'une liste mais en itérant vers le haut et cela a foiré la boucle parce que l'entrée n'existait plus:
la source
J'ai vu de nombreuses options pour cela, mais pour moi, celle-ci était la meilleure.
Parcourez ensuite simplement la collection.
N'oubliez pas qu'un ListItemCollection peut contenir des doublons. Par défaut, rien n'empêche l'ajout de doublons à la collection. Pour éviter les doublons, vous pouvez procéder comme suit:
la source
La réponse acceptée est imprécise et incorrecte dans le pire des cas. Si des modifications sont apportées pendant
ToList()
, vous pouvez toujours vous retrouver avec une erreur. En outrelock
, quelles sont les performances et la sécurité des threads à prendre en compte si vous avez un membre public, une solution appropriée peut utiliser des types immuables .En général, un type immuable signifie que vous ne pouvez pas en changer l'état une fois créé. Votre code devrait donc ressembler à:
Cela peut être particulièrement utile si vous avez un membre public. Les autres classes peuvent toujours
foreach
sur les types immuables sans se soucier de la modification de la collection.la source
Voici un scénario spécifique qui justifie une approche spécialisée:
Dictionary
est fréquemment énuméré.Dictionary
est rarement modifié.Dans ce scénario, la création d'une copie du
Dictionary
(ou duDictionary.Values
) avant chaque énumération peut être assez coûteuse. Mon idée sur la résolution de ce problème est de réutiliser la même copie mise en cache dans plusieurs énumérations et de regarder unIEnumerator
de l'originalDictionary
pour les exceptions. L'énumérateur sera mis en cache avec les données copiées et interrogé avant de commencer une nouvelle énumération. En cas d'exception, la copie mise en cache sera supprimée et une nouvelle sera créée. Voici ma mise en œuvre de cette idée:Exemple d'utilisation:
Malheureusement, cette idée ne peut pas être utilisée actuellement avec la classe
Dictionary
dans .NET Core 3.0, car cette classe ne renvoie pas d' exception Collection a été modifiée lors de l'énumération et des méthodesRemove
etClear
sont invoquées. Tous les autres conteneurs que j'ai vérifiés se comportent de manière cohérente. J'ai vérifié systématiquement ces classes:List<T>
,Collection<T>
,ObservableCollection<T>
,HashSet<T>
,SortedSet<T>
,Dictionary<T,V>
etSortedDictionary<T,V>
. Seules les deux méthodes susmentionnées de laDictionary
classe dans .NET Core n'invalident pas l'énumération.Mise à jour: j'ai résolu le problème ci-dessus en comparant également les longueurs de la collection mise en cache et de la collection d'origine. Ce correctif suppose que le dictionnaire sera transmis directement comme argument pour le
EnumerableSnapshot
constructeur de », et son identité ne sera pas caché par (par exemple) une projection comme:dictionary.Select(e => e).ΤοEnumerableSnapshot()
.Important: La classe ci-dessus n'est pas thread-safe. Il est destiné à être utilisé à partir de code s'exécutant exclusivement dans un seul thread.
la source
Vous pouvez copier un objet de dictionnaire d'abonnés dans un même type d'objet de dictionnaire temporaire, puis itérer l'objet de dictionnaire temporaire à l'aide de la boucle foreach.
la source
Donc, une autre façon de résoudre ce problème serait de supprimer un élément pour créer un nouveau dictionnaire et d'ajouter uniquement les éléments que vous ne vouliez pas supprimer puis de remplacer le dictionnaire d'origine par le nouveau. Je ne pense pas que ce soit trop un problème d'efficacité car cela n'augmente pas le nombre de fois que vous parcourez la structure.
la source
Il y a un lien où il a très bien élaboré et une solution est également donnée. Essayez-le si vous avez la bonne solution, veuillez poster ici afin que d'autres puissent comprendre. La solution donnée est ok alors comme le poste afin que d'autres puissent essayer ces solutions.
pour vous référence le lien d'origine: - https://bensonxion.wordpress.com/2012/05/07/07/serializing-an-ienumerable-produces-collection-was-modified-enumeration-operation-may-not-execute/
Lorsque nous utilisons des classes de sérialisation .Net pour sérialiser un objet dont sa définition contient un type Enumerable, c'est-à-dire une collection, vous obtiendrez facilement InvalidOperationException disant "La collection a été modifiée; l'opération d'énumération peut ne pas s'exécuter" lorsque votre codage est dans des scénarios multi-threads. La cause inférieure est que les classes de sérialisation vont parcourir la collection via l'énumérateur, en tant que tel, le problème va essayer d'itérer à travers une collection tout 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. Évidemment, vous obtiendrez une pénalité de performance si vous voulez sérialiser une collection de cet objet, alors pour chacun d'eux, le verrou sera appliqué.
Eh bien, .Net 4.0 qui rend le traitement des scénarios multi-threading très pratique. pour ce problème de champ de collection de sérialisation, j'ai trouvé que nous pouvions simplement profiter de la classe ConcurrentQueue (Check MSDN), qui est une collection thread-safe et FIFO et rend le code sans verrouillage.
En utilisant cette classe, dans sa simplicité, les éléments que vous devez modifier pour votre code remplacent le type Collection par celui-ci, utilisez Enqueue pour ajouter un élément à la fin de ConcurrentQueue, supprimez ces codes de verrouillage. Ou, si le scénario sur lequel vous travaillez nécessite des éléments de collecte tels que List, vous aurez besoin d'un peu plus de code 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 l'effacement atomique de la collection. vous devez donc le faire vous-même, le moyen le plus rapide est de recréer un nouveau ConcurrentQueue vide pour un remplacement.
la source
J'ai personnellement rencontré cela récemment dans un code inconnu où il se trouvait dans un dbcontext ouvert avec .Remove (item) de Linq. J'ai eu énormément de temps à localiser le débogage d'erreur parce que les éléments avaient été supprimés la première fois, et si j'essayais de prendre du recul et de recommencer, la collection était vide, car j'étais dans le même contexte!
la source