Threadsafe pour chaque énumération de listes

J’ai besoin d’énumérer si générique IList d’objets. Le contenu de la liste peut changer, par exemple en étant ajouté ou supprimé par d’autres threads, ce qui va tuer mon énumération avec un “La collection a été modifiée; l’opération d’énumération peut ne pas s’exécuter”.

Quel est le bon moyen de faire en sorte que threadsafe se produise sur un IList ? préférablement sans cloner toute la liste. Il n’est pas possible de cloner les objets réels référencés par la liste.

Le clonage de la liste est la méthode la plus simple et la meilleure, car elle garantit que votre liste ne changera pas. Si la liste est simplement trop longue pour être clonée, envisagez de la verrouiller avant de la lire / écrire.

Votre problème est qu’une énumération ne permet pas à l’IList de changer. Cela signifie que vous devez éviter cela en parcourant la liste.

Quelques possibilités me viennent à l’esprit:

  • Cloner la liste. Maintenant, chaque énumérateur a sa propre copie sur laquelle travailler.
  • Sérialiser l’access à la liste. Utilisez un verrou pour vous assurer qu’aucun autre thread ne peut le modifier pendant son énumération.

Vous pouvez également écrire votre propre implémentation d’IList et IEnumerator qui permet le type d’access parallèle dont vous avez besoin. Cependant, j’ai bien peur que cela ne soit pas simple.

Vous constaterez que c’est un sujet très intéressant.

La meilleure approche repose sur ReadWriteResourceLock, qui entraînait de gros problèmes de performances en raison du problème appelé Convoy.

Le meilleur article que j’ai trouvé traitant du sujet est celui de Jeffrey Richter, qui expose sa propre méthode pour une solution de haute performance.

Il n’y a pas une telle opération. Le mieux que vous puissiez faire est

lock(collection){ foreach (object o in collection){ ... } } 

Forech dépend du fait que la collection ne changera pas. Si vous souhaitez parcourir une collection qui peut changer, utilisez la construction normale pour et soyez prêt à un comportement non déterministe. Le locking pourrait être une meilleure idée, selon ce que vous faites.

 ICollection MyCollection; // Instantiate and populate the collection lock(MyCollection.SyncRoot) { // Some operation on the collection, which is now thread safe. } 

À partir de MSDN

Les conditions sont donc les suivantes: vous devez énumérer une liste IList <> sans en faire une copie tout en ajoutant et en supprimant simultanément des éléments.

Pourriez-vous clarifier quelques points? Les insertions et les suppressions se produisent-elles uniquement au début ou à la fin de la liste? Si des modifications peuvent survenir à tout moment dans la liste, comment l’énumération doit-elle se comporter lorsque des éléments sont supprimés ou ajoutés à proximité ou à l’emplacement de l’élément actuel de l’énumération?

Cela est certainement faisable en créant un object IEnumerable personnalisé avec peut-être un index entier, mais uniquement si vous pouvez contrôler tous les access à votre object IList <> (pour verrouiller et conserver l’état de votre énumération). Mais la programmation multithread est une affaire délicate dans les meilleures circonstances, et c’est un problème complexe.

Le comportement par défaut pour une structure de données indexée simple, telle qu’une liste chaînée, un arbre B ou une table de hachage, doit être énuméré dans l’ordre, du premier au dernier. Cela ne poserait pas de problème d’insérer un élément dans la structure de données après que l’iterator l’aurait dépassé ou d’en insérer un que l’iterator énumérerait une fois qu’il serait arrivé, et un tel événement pourrait être détecté par l’application et traité si l’application l’exigeait. Pour détecter un changement dans la collection et émettre une erreur lors de l’énumération, je ne pouvais imaginer que quelqu’un (mal) avait la mauvaise idée de faire ce que le programmeur pensait vouloir. En effet, Microsoft a corrigé leurs collections pour fonctionner correctement. Ils ont appelé leurs nouvelles collections ininterrompues shinyes ConcurrentCollections (System.Collections.Concurrent) dans .NET 4.0.

Enveloppez la liste dans un object de locking pour la lecture et l’écriture. Vous pouvez même itérer avec plusieurs lecteurs à la fois si vous avez un verrou approprié, qui permet plusieurs lecteurs simultanés mais également un seul rédacteur (lorsqu’il n’y a pas de lecteurs).