32

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.

Joan Venge
  • 315,713
  • 212
  • 479
  • 689
  • 1
    check this out: http://stackoverflow.com/questions/528999/why-arent-classes-like-bindinglist-or-observablecollection-thread-safe – geofftnz Mar 29 '11 at 23:59
  • 1
    Exact duplicate: http://stackoverflow.com/questions/187069/can-not-operate-observablecollection-in-multi-threads. Mark Ingram's answer appears to be what you are looking for. – Chris Laplante Mar 30 '11 at 00:14
  • I looked at Mark's answer but couldn't figure it out. It uses Monitor but what's the DoEvents method? Also how does it work, the try catch is outside the while. Is the lock still in effect at that point? – Joan Venge Mar 30 '11 at 00:28
  • Try the following link which provides a thread-safe solution that works from any thread and can be bound to via multiple UI threads : http://www.codeproject.com/Articles/64936/Multithreaded-ObservableImmutableCollection – Anthony Apr 15 '14 at 19:24

5 Answers5

36

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.
    });
mixel
  • 25,177
  • 13
  • 126
  • 165
ywm
  • 411
  • 1
  • 4
  • 4
12

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();
    }
}
djeeg
  • 6,685
  • 3
  • 25
  • 28
  • Thanks but RunWorkerCompleted is gonna run after BGW is exited? Because I want the UI to update at each iteration so I can see all the changes immediately. – Joan Venge Mar 30 '11 at 00:40
  • you can use RunWorkerCompleted to do the partial updates, let me update the code – djeeg Mar 30 '11 at 00:44
  • Thanks, I didn't know you could do that, it would be awesome if this solves it. Let me try. – Joan Venge Mar 30 '11 at 00:58
  • Btw how am I gonna access the values I calculate inside the BGW? I thought I could just set e.Result and access it from ProgressChanged but it doesn't allow me to do so. – Joan Venge Mar 30 '11 at 01:06
  • use the second argument in ReportProgress(), it goes to ProgressChanged as e.UserState. when you start needing to access an object shared in both the DoWork and ProgressChanged methods, you have to start worrying about thread safety and you will need a locking strategy. – djeeg Mar 30 '11 at 01:17
  • Sorry I was away for a couple of days. Just coming back to this. I am doing it step by step and my first step was using the bgworker.ProgressChanged += (s, e) => { ... } but that doesn't seem to be called. I assign the progressbar progress from e.ProgressPercentage, but not only it doesn't do anything, but I also can't step into this event. It doesn't seem to be connected. Do you know why this might be? – Joan Venge Apr 01 '11 at 17:24
  • I found it it was because I didn't raise the event, just thought that was done automatically by the BGW. – Joan Venge Apr 01 '11 at 17:37
  • Some explanation of what you are doing rather than just a code dump would be nice – MikeT Feb 26 '16 at 17:12
4

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.

Hans Passant
  • 922,412
  • 146
  • 1,693
  • 2,536
  • Thanks Hans. How do I know I created it on the UI thread? I basically created the BGW inside my VM object. Also I am not updating the ObservableCollection, but the ObservableCollection within each item inside the main ObservableCollection. Because every item has a list of values that they hold, I want to update these ones. I can update the other properties of each item inside the BGW but not call Add on this nested ObservableCollection. – Joan Venge Mar 30 '11 at 00:38
  • I dunno, you are talking about details of code that I cannot see. Cold hard fact is that you cannot update whatever you are updating in DoWork. Use one of the event handlers. Use a helper collection if necessary to store intermediary results. – Hans Passant Mar 30 '11 at 00:46
  • It works if I store the collection, add 1 item and replace it, but that seemed like a hack. Do you think this is bad practice? – Joan Venge Mar 30 '11 at 00:58
3

Try This:

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

 //Code

}));
Nalan Madheswaran
  • 10,136
  • 1
  • 57
  • 42
1

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?)

Community
  • 1
  • 1
Daniele Armanasco
  • 7,289
  • 9
  • 43
  • 55