68 votes

Où puis-je me procurer un CollectionView thread-safe?

Lors de la mise à jour d'une collection d'objets métier sur un fil d'arrière-plan, le message d'erreur suivant s'affiche:

Ce type de CollectionView ne prend pas en charge les modifications apportées à SourceCollection à partir d'un thread différent du thread Dispatcher.

Ok, ça a du sens. Mais cela pose également la question suivante: quelle version de CollectionView prend en charge plusieurs threads et comment puis-je faire en sorte que mes objets l'utilisent?

88voto

luke Points 771

Utilisation:

 System.Windows.Application.Current.Dispatcher.Invoke(
    System.Windows.Threading.DispatcherPriority.Normal,
    (Action)delegate() 
    {
         // Your Action Code
    });
 

64voto

Nathan Phillips Points 2849

Ce qui suit est une amélioration sur la mise en œuvre trouvés par Jonathan. Tout d'abord, il s'exécute à chaque gestionnaire d'événement sur le répartiteur associés avec elle plutôt que de supposer qu'ils sont tous sur le même (UI) répartiteur. Deuxièmement, il utilise BeginInvoke pour permettre de continuer le traitement pendant que nous attendons pour le répartiteur de devenir disponibles. Cela rend la solution beaucoup plus rapide dans les situations où le thread d'arrière-plan est de faire beaucoup de mises à jour avec traitement entre chacun d'entre eux. Peut-être plus important encore, il permet de surmonter les problèmes causés par le blocage lors de l'attente pour l'Invoquer (blocages peuvent se produire par exemple lors de l'utilisation de WCF avec ConcurrencyMode.Unique).

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);
            }
    }
}

Parce que nous sommes à l'aide de BeginInvoke, il est possible que le changement ait été notifiée, est annulée avant que le gestionnaire est appelé. Ce serait normalement entraîner un "Index était hors de portée." exception levée lorsque les arguments de l'événement sont vérifiées par rapport à la nouvelle (modifié) l'état de la liste. Afin d'éviter cela, tous les retardée événements sont remplacés par Réinitialiser les événements. Cela pourrait provoquer un excès de redessiner dans certains cas.

17voto

Cameron MacFarland Points 27240

Ce post par Bea Stollnitz explique que le message d'erreur et pourquoi il est libellée de la façon dont il est.

EDIT: à Partir du Bea blog

Malheureusement, ce code entraîne une exception: "NotSupportedException – Ce type de CollectionView ne prend pas en charge les modifications de son SourceCollection à partir d'un thread différent de l'expéditeur au fil." Je comprends ce message d'erreur qui conduit les gens à penser que, si CollectionView qu'ils utilisent ne prend pas en charge inter-threads, alors qu'ils ont à trouver celui qui n'. Eh bien, ce message d'erreur est un peu trompeur: aucun des CollectionViews nous fournir sur le soutient de la croix-fil de la collecte des changements. Et non, malheureusement nous ne pouvons pas résoudre le message d'erreur à ce point, nous sommes très verrouillé.

7voto

Jonathan Allen Points 23540

Trouvé un.

 public class MTObservableCollection<T> : ObservableCollection<T>
{
   public override event NotifyCollectionChangedEventHandler CollectionChanged;
   protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
   {
      var eh = CollectionChanged;
      if (eh != null)
      {
         Dispatcher dispatcher = (from NotifyCollectionChangedEventHandler nh in eh.GetInvocationList()
                 let dpo = nh.Target as DispatcherObject
                 where dpo != null
                 select dpo.Dispatcher).FirstOrDefault();

        if (dispatcher != null && dispatcher.CheckAccess() == false)
        {
           dispatcher.Invoke(DispatcherPriority.DataBind, (Action)(() => OnCollectionChanged(e)));
        }
        else
        {
           foreach (NotifyCollectionChangedEventHandler nh in eh.GetInvocationList())
              nh.Invoke(this, e);
        }
     }
  }
}
 

http://www.julmar.com/blog/mark/2009/04/01/AddingToAnObservableCollectionFromABackgroundThread.aspx

3voto

Richard Points 21

Vous pouvez également consulter: BindingOperations.EnableCollectionSynchronization .

Voir Mise à niveau vers .NET 4.5: Un ItemsControl est incompatible avec la source de ses éléments

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