Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What's the best way to update an ObservableCollection from another thread?

I am using the BackgroundWorker to update an ObservableCollection but it gives this error:

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

What's the best and most elegant way to solve this, with the least amount of work. I don't want to write low level lock-based multi-threading code.

I have seen some solutions online but they are several years old, so not sure what the latest consensus is for the solution of this problem.

like image 322
Joan Venge Avatar asked Mar 29 '11 23:03

Joan Venge


5 Answers

If you initialize the collection in the constructor it will be on the default Application thread.

To invoke the main thread you can do this:

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

You have to cast the Anonymous delegate as an action otherwise it gets confused ¯\O_o/¯

If you are using the Async CTP then you can do this

Application.Current.Dispatcher.InvokeAsync(()=>
    {
       //Do something here.
    });
like image 160
ywm Avatar answered Nov 17 '22 02:11

ywm


If 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();
    }
}
like image 24
djeeg Avatar answered Nov 17 '22 01:11

djeeg


You are using BGW, it was designed to solve your problem. But you'll have to use it properly, update the collection in a ProgressChanged or RunWorkerCompleted event handler. If that's what you are doing then you created the BGW instance on the wrong thread. It has to be done on the UI thread.

like image 4
Hans Passant Avatar answered Nov 17 '22 01:11

Hans Passant


Try This:

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

 //Code

}));
like image 3
Nalan Madheswaran Avatar answered Nov 17 '22 01:11

Nalan Madheswaran


I had the same problem while reloading an observableCollection from an event (on DataReceived) raised by the serial port class. I used MVVM; I tried to update the collection with the BackgroundWorker, but it raised the "This type of CollectionView does not support changes to its SourceCollection from a thread different from the Dispatcher thread.". I tried many others solutions found online, but the only one that solved my problem (immediately!) was to use a multi-threading observable collection (I used the one in this post: Where do I get a thread-safe CollectionView?)

like image 1
Daniele Armanasco Avatar answered Nov 17 '22 02:11

Daniele Armanasco