32

Je me sers du BackgroundWorker de mettre à jour un ObservableCollection mais il donne cette erreur:Quelle est la meilleure façon de mettre à jour un ObservableCollection à partir d'un autre thread?

"This type of CollectionView does not support changes to its SourceCollection from a thread different from the Dispatcher thread."

Quelle est la meilleure et la plus élégante de résoudre ce, avec le moins de travail. Je ne veux pas écrire de code multi-threading bas niveau basé sur le verrouillage.

J'ai vu quelques solutions en ligne mais elles ont plusieurs années, donc je ne sais pas quel est le consensus le plus récent pour la solution de ce problème.

+1

vérifier cela: http://stackoverflow.com/questions/528999/why-arent-classes-like-bindinglist-or-observablecollection-thread-safe – geofftnz

+1

Exact duplicate: http://stackoverflow.com/questions/ 187069/impossible d'exploiter-observerablecollection-in-multi-threads. La réponse de Mark Ingram semble être ce que vous cherchez. –

+0

J'ai regardé la réponse de Mark mais je n'ai pas compris. Il utilise Monitor mais quelle est la méthode DoEvents? Aussi comment ça marche, la prise d'essai est en dehors du moment. Le verrou est-il toujours en vigueur à ce moment-là? –

Répondre

12

Si MVVM

public class MainWindowViewModel : ViewModel { 

    private ICommand loadcommand; 
    public ICommand LoadCommand { get { return loadcommand ?? (loadcommand = new RelayCommand(param => Load())); } } 

    private ObservableCollection<ViewModel> items; 
    public ObservableCollection<ViewModel> Items { 
     get { 
      if (items == null) { 
       items = new ObservableCollection<ViewModel>(); 
      } 
      return items; 
     } 
    } 

    public void Load() { 
     BackgroundWorker bgworker = new BackgroundWorker(); 
     bgworker.WorkerReportsProgress = true; 
     bgworker.DoWork += (s, e) => { 
      for(int i=0; i<10; i++) { 
       System.Threading.Thread.Sleep(1000); 
       bgworker.ReportProgress(i, new List<ViewModel>()); 
      } 
      e.Result = null; 
     }; 
     bgworker.ProgressChanged += (s, e) => { 
      List<ViewModel> partialresult = (List<ViewModel>)e.UserState; 
      partialresult.ForEach(i => { 
       Items.Add(i); 
      }); 
     }; 
     bgworker.RunWorkerCompleted += (s, e) => { 
      //do anything here 
     }; 
     bgworker.RunWorkerAsync(); 
    } 
} 
+0

Merci, mais RunWorkerCompleted va fonctionner après la fermeture de BGW? Parce que je veux que l'interface utilisateur mette à jour à chaque itération afin que je puisse voir tous les changements immédiatement. –

+0

vous pouvez utiliser RunWorkerCompleted pour faire les mises à jour partielles, laissez-moi mettre à jour le code – djeeg

+0

je veux dire "ProgressChanged" – djeeg

4

Vous utilisez BGW, il a été conçu pour résoudre votre problème. Mais vous devrez l'utiliser correctement, mettre à jour la collection dans un gestionnaire d'événements ProgressChanged ou RunWorkerCompleted. Si c'est ce que vous faites, vous avez créé l'instance BGW sur le mauvais thread. Cela doit être fait sur le thread de l'interface utilisateur.

+0

Merci Hans. Comment puis-je savoir que je l'ai créé sur le fil de l'interface utilisateur? J'ai fondamentalement créé le BGW dans mon objet VM. Je ne suis pas non plus en train de mettre à jour ObservableCollection, mais la collection ObservableCollection dans chaque élément de la collection ObservableCollection principale. Parce que chaque élément a une liste de valeurs qu'ils détiennent, je veux mettre à jour ceux-ci. Je peux mettre à jour les autres propriétés de chaque élément à l'intérieur de la BGW mais ne pas appeler Add sur ce ObservableCollection imbriqué. –

+0

Je ne sais pas, vous parlez de détails de code que je ne peux pas voir.Le fait est que vous ne pouvez pas mettre à jour tout ce que vous mettez à jour dans DoWork. Utilisez l'un des gestionnaires d'événements. Utilisez une collection d'aide si nécessaire pour stocker les résultats intermédiaires. –

+0

Cela fonctionne si je stocke la collection, ajoute 1 élément et le remplace, mais cela a semblé comme un hack. Pensez-vous que c'est une mauvaise pratique? –

36

Si vous initialisez la collection dans le constructeur, il sera sur la valeur par défaut fil d'application.

Pour appeler le fil principal, vous pouvez le faire:

Application.Current.Dispatcher.Invoke((Action)(() => 
    { 
     //Do something here. 
    })); 

Vous devez lancer le délégué Anonymous comme une action sinon il se confond ¯ \ O_o/¯

Si vous utilisez le Async CTP alors vous pouvez le faire

Application.Current.Dispatcher.InvokeAsync(()=> 
    { 
     //Do something here. 
    }); 
+0

merci! travaille pour moi :) –

1

J'ai eu le même problème en rechargeant ObservableCollection d'un événement (sur DataReceived) soulevée par la classe de port série. J'ai utilisé MVVM; J'ai essayé de mettre à jour la collection avec BackgroundWorker, mais il a soulevé le "Ce type de CollectionView ne prend pas en charge les modifications apportées à sa SourceCollection à partir d'un thread différent du thread Dispatcher.". J'ai essayé beaucoup d'autres solutions trouvées en ligne, mais le seul qui a résolu mon problème (! Immédiatement) consiste à utiliser une collection observable multi-threading (je celui de ce poste: Where do I get a thread-safe CollectionView?)

3

Essayez ceci:

this.Dispatcher.Invoke(DispatcherPriority.Background, new Action(
() => 
{ 

//Code 

})); 
Questions connexes