web-dev-qa-db-fra.com

Quelle est la meilleure façon de mettre à jour une ObservableCollection à partir d'un autre thread?

J'utilise le BackgroundWorker pour mettre à jour un ObservableCollection mais cela donne cette erreur:

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

Quelle est la meilleure et la plus élégante façon de résoudre ce problème, avec le moins de travail. Je ne veux pas écrire de code multithread basé sur un verrouillage de bas niveau.

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

32
Joan Venge

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();
    }
}
12
djeeg

Si vous initialisez la collection dans le constructeur, elle se trouvera sur le thread d'application par défaut.

Pour appeler le thread principal, vous pouvez procéder comme suit:

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

Vous devez lancer le délégué anonyme en tant qu'action sinon il se confond ¯\O_o/¯

Si vous utilisez le CTP Async, vous pouvez le faire

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

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 d'interface utilisateur.

4
Hans Passant

Essaye ça:

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

 //Code

}));
3
Nalan Madheswaran

J'ai eu le même problème lors du rechargement d'une observableCollection à partir d'un événement (sur DataReceived) déclenché par la classe de port série. J'ai utilisé MVVM; J'ai essayé de mettre à jour la collection avec le BackgroundWorker, mais il a déclenché "ce type de CollectionView ne prend pas en charge les modifications apportées à son SourceCollection à partir d'un thread différent du thread Dispatcher.". J'ai essayé de nombreuses autres solutions trouvées en ligne, mais la seule qui a résolu mon problème (immédiatement!) Était d'utiliser une collection observable multi-threading (j'ai utilisé celle dans cet article: Où puis-je obtenir un thread-safe CollectionView? )

1
Daniele Armanasco