web-dev-qa-db-fra.com

Comment rendre ObservableCollection thread-safe?

System.InvalidOperationException: Collection was modified; enumeration operation may not execute.

J'ajoute/supprime une ObservableCollection qui n'est pas sur un thread d'interface utilisateur.

J'ai une méthode nommée EnqueueReport à ajouter à la colleciton et une DequeueReport à retirer de la colleciton.

Le déroulement des étapes est le suivant: -

  1. 1.call EnqueueReport chaque fois qu'un nouveau rapport est demandé
  2. appeler une méthode toutes les quelques secondes pour vérifier si le rapport est généré (cela a une boucle foreach qui vérifie l'état généré de tous les rapports dans ObservableCollection)
  3. appeler DequeueReport si le rapport est généré

Je ne suis pas beaucoup dans les bibliothèques C #. Quelqu'un peut-il me guider à ce sujet?

19
ilight

Vous pouvez créer une version simple et conviviale des threads de la collection observable. Comme le suivant:

 public class MTObservableCollection<T> : ObservableCollection<T>
    {
        public override event NotifyCollectionChangedEventHandler CollectionChanged;
        protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
        {
            NotifyCollectionChangedEventHandler CollectionChanged = this.CollectionChanged;
            if (CollectionChanged != null)
                foreach (NotifyCollectionChangedEventHandler nh in CollectionChanged.GetInvocationList())
                {
                    DispatcherObject dispObj = nh.Target as DispatcherObject;
                    if (dispObj != null)
                    {
                        Dispatcher dispatcher = dispObj.Dispatcher;
                        if (dispatcher != null && !dispatcher.CheckAccess())
                        {
                            dispatcher.BeginInvoke(
                                (Action)(() => nh.Invoke(this,
                                    new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset))),
                                DispatcherPriority.DataBind);
                            continue;
                        }
                    }
                    nh.Invoke(this, e);
                }
        }
    }

avec cela, faites maintenant une recherche et un remplacement massifs et changez tous vos ObservableCollection en MTObservableCollection et votre bon aller

7
Franck

Depuis .net framwork 4.5, vous pouvez utiliser la synchronisation de collection native.

BindingOperations.EnableCollectionSynchronization(YourCollection, YourLockObject);

YourLockObject est une instance de n'importe quel objet, par exemple new Object();. Utilisez-en un par collection.

Cela élimine le besoin d'une classe spéciale ou quoi que ce soit. Il suffit d'activer et de profiter;)

[modifier] Comme indiqué dans les commentaires de Mark et Ed (merci pour la clarification!), cela ne pas vous soulage de verrouiller la collection sur les mises à jour car il synchronise simplement la collection-view-binding et ne rend pas comme par magie la collection thread-safe elle-même. [/ modifier]

PS: BindingOperations réside dans l'espace de noms System.Windows.Data.

26
FastJack

La solution que Franck a postée ici fonctionnera dans le cas où un thread ajoute des choses, mais ObservableCollection lui-même (et List, sur lequel il est basé) ne sont pas thread-safe. Si plusieurs threads écrivent dans la collection, des bogues difficiles à localiser peuvent être introduits. J'ai écrit une version d'ObservableCollection qui utilise un ReaderWriteLockSlim pour être vraiment thread-safe.

Malheureusement, il a atteint la limite de caractères StackOverflow, donc ici c'est sur Pastebin. Cela devrait fonctionner à 100% avec plusieurs lecteurs/écrivains. Tout comme ObservableCollection ordinaire, il n'est pas valide de modifier la collection dans un rappel à partir d'elle (sur le thread qui a reçu le rappel).

19
Robert Fraser

Vous pouvez utiliser une classe ObservableConcurrentCollection. Ils se trouvent dans un package fourni par Microsoft dans la bibliothèque Parallel Extensions Extras.

Vous pouvez le faire préconstruire par la communauté sur Nuget: https://www.nuget.org/packages/ParallelExtensionsExtras/

Ou obtenez-le auprès de Microsoft ici:

https://code.msdn.Microsoft.com/ParExtSamples

10
Greg