2016-12-08 2 views
6

J'ai rencontré un problème intéressant. Sachant que le ConcurrentDictionary<TKey, TValue> est enumerable en toute sécurité en étant modifié, avec (dans mon cas) un effet secondaire indésirable d'itération sur les éléments qui peuvent disparaître ou apparaître plusieurs fois, j'ai décidé de créer moi-même un snapshot en utilisant ToList(). Comme ConcurrentDictionary<TKey, TValue> implémente également ICollection<KeyValuePair<TKey, TValue>>, cela provoque l'utilisation du List(IEnumerable<T> collection), qui à son tour crée un tableau dans la taille actuelle du dictionnaire en utilisant l'élément actuel Count, puis tente de copier les éléments using ICollection<T>.CopyTo(T[] array, int arrayIndex), appelant dans son implémentation ConcurrentDictionary<TKey, TValue>, et enfin lancer un ArgumentException si des éléments sont ajoutés au dictionnaire entre-temps. En verrouillant tout cela, l'option d'utilisation de la collection serait supprimée, donc mes options semblent être soit de continuer à attraper l'exception et de réessayer (ce qui n'est certainement pas la bonne réponse au problème), soit d'implémenter mon propre version de ToList() spécialisée pour ce problème (mais encore une fois, en augmentant simplement une liste, puis en l'ajustant à la bonne taille pour quelques éléments semble être une surcharge, et en utilisant une LinkedList diminuerait les performances d'indexation). En outre, il semble que l'ajout de certaines méthodes LINQ qui créent une sorte de tampon en arrière-plan (comme OrderBy) semble améliorer le problème au détriment de la performance, mais il est évident que le ToList() nu ne le fait pas, et il ne vaut pas la peine de "l'augmenter" avec une autre méthode lorsqu'aucune fonctionnalité supplémentaire n'est nécessaire.Appel de ToList() sur ConcurrentDictionary <TKey, TValue> lors de l'ajout d'éléments

Cela peut-il être un problème avec une collection simultanée?

Quelle serait une solution de contournement raisonnable pour maintenir les performances atteintes au minimum lors de la création d'un tel instantané? (De préférence à la fin de la magie LINQ.)

Edit:

Après avoir examiné ce que je peux confirmer, ToArray() (penser que je viens de passer par hier) fait vraiment résoudre le problème de l'instantané comme Bien que ce soit juste un instantané simple, cela n'aide pas lorsque des fonctionnalités supplémentaires sont nécessaires avant de prendre l'instantané (comme le filtrage, le tri), et une liste/tableau est toujours nécessaire à la fin. (Dans ce cas, un appel supplémentaire est nécessaire, créant à nouveau la nouvelle collection.)

Je n'ai pas indiqué que l'instantané peut ou ne doit pas subir ces modifications, il doit donc être pris à la fin, de préférence, donc j'ajouterais cela aux questions.

(Aussi, si quelqu'un a une meilleure idée pour un titre, ne le dire.)

+2

Utilisez plutôt '.ToArray()' qui est implémenté spécifiquement par le type. –

Répondre

6

Répondons large adombrante question ici pour tous les types simultanés:

Si vous découpez une opération qui traite les internes en plusieurs étapes, où toutes les étapes doivent être "synchronisées", puis oui, définitivement vous obtiendrez des plantages et des résultats impairs dus à la synchronisation des threads.

Donc, si vous utilisez .ToList() va d'abord demander .Count, puis la taille d'un tableau, puis utilisez foreach pour saisir les valeurs et les placer dans la liste, puis oui, définitivement vous aurez la chance des deux parties obtenir un nombre différent d'éléments.

Pour être honnête, je souhaite que certains de ces types concurrents n'aient pas essayé de prétendre qu'ils étaient des collections normales en implémentant beaucoup de ces interfaces mais hélas, c'est comme ça. Pouvez-vous corriger votre code maintenant que vous connaissez le problème?

Oui vous pouvez, vous devez jeter un oeil à la documentation de type et voir si elle fournit n'importe quelle forme de mécanisme d'instantané qui n'est pas sujette aux problèmes mentionnés ci-dessus.

tours dehors ConcurrentDictionary<TKey, TValue> outils .ToArray(), ce qui est documented avec:

Une nouvelle matrice contenant un instantané paires de clés et de valeurs copiées à partir du System.Collections.Concurrent.ConcurrentDictionary.

(je souligne)

Comment est .ToArray() actuellement mis en œuvre?

Using locks, voir la ligne 697.

Donc, si vous vous sentez verrouiller le dictionnaire entier pour obtenir un aperçu est trop coûteuse je mets en doute le fait de saisir un instantané de son contenu pour commencer. mais il ne

Le recenseur est revenu du dictionnaire est sûr à utiliser simultanément avec lit et écrit dans le dictionnaire,:

De plus, la méthode .GetEnumerator() suit certaines des mêmes règles, de la documentation ne représente pas un instantané instantané du dictionnaire. Le contenu exposé par l'intermédiaire de l'énumérateur peut contenir des modifications apportées au dictionnaire après GetEnumerator a été appelé.

(encore une fois, mon emhpasis)

Ainsi, alors que .GetEnumerator() ne sera pas accident, il peut ne pas produire les résultats que vous voulez.

En fonction de la synchronisation, aucun ne peut .ToArray(), donc tout dépend.