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

107,806

Solution 1

Since your ObservableCollection is created on UI thread, you can only modify it from UI thread and not from other threads. This is termed as thread affinity.

If you ever need to update objects created on UI thread from different thread, simply put the delegate on UI Dispatcher and that will do work for you delegating it to UI thread. This will work -

    public void Load()
    {
        matchList = new List<GetMatchDetailsDC>();
        matchList = proxy.GetMatch().ToList();

        foreach (EfesBet.DataContract.GetMatchDetailsDC match in matchList)
        {
            App.Current.Dispatcher.Invoke((Action)delegate // <--- HERE
            {
                _matchObsCollection.Add(match);
            });
        }
    }

Solution 2

If I'm not mistaken, in WPF 4.5, you should be able to do this without any problem.

Now to solve this, you should use the synchronization context. Before you launch the thread, you have to store the synchronization context in the ui thread.

var uiContext = SynchronizationContext.Current;

Then you use it in your thread:

uiContext.Send(x => _matchObsCollection.Add(match), null);

Take a look at this tuto http://www.codeproject.com/Articles/31971/Understanding-SynchronizationContext-Part-I

Solution 3

You can do this:

App.Current.Dispatcher.Invoke((System.Action)delegate
             {
               _matchObsCollection.Add(match)
             });

For .NET 4.5+: You can follow the answer of Daniel. In his example you give the responsability to the publisher that they need to call or invoke on the correct thread:

var uiContext = SynchronizationContext.Current;
uiContext.Send(x => _matchObsCollection.Add(match), null);

Or you could put the responsability to your service/viewmodel/whatever and simply enable CollectionSynchronization. This way if you make a call you don't have to worry on which thread you are on and on which one you make the call. The responsability is not for the Publisher anymore. (This may give you a little performance overhead but doing this in a central service, it can save you a lot of exceptions and gives you easier application maintenance.)

private static object _lock = new object();

public MainWindowViewModel()
{
    // ...
    _matchObsCollection = new ObservableCollection<EfesBet.DataContract.GetMatchDetailsDC>();
    BindingOperations.EnableCollectionSynchronization(_matchObsCollection , _lock);
} 

More info: https://msdn.microsoft.com/en-us/library/system.windows.data.bindingoperations.enablecollectionsynchronization(v=vs.110).aspx

In Visual Studio 2015 (Pro) go to Debug --> Windows --> Threads to easily debug and see on which threads you are on.

Solution 4

I have experienced the same issue once and resolved the issue with AsyncObservableCollection (http://www.thomaslevesque.com/2009/04/17/wpf-binding-to-an-asynchronous-collection/).

Solution 5

I have found a solution here: https://www.thomaslevesque.com/2009/04/17/wpf-binding-to-an-asynchronous-collection/ You just create a new class and use it instead of ObservableCollection. It worked for me.

public class AsyncObservableCollection<T> : ObservableCollection<T>
{
    private SynchronizationContext _synchronizationContext = SynchronizationContext.Current;

    public AsyncObservableCollection()
    {
    }

    public AsyncObservableCollection(IEnumerable<T> list)
        : base(list)
    {
    }

    protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
    {
        if (SynchronizationContext.Current == _synchronizationContext)
        {
            // Execute the CollectionChanged event on the current thread
            RaiseCollectionChanged(e);
        }
        else
        {
            // Raises the CollectionChanged event on the creator thread
            _synchronizationContext.Send(RaiseCollectionChanged, e);
        }
    }

    private void RaiseCollectionChanged(object param)
    {
        // We are in the creator thread, call the base implementation directly
        base.OnCollectionChanged((NotifyCollectionChangedEventArgs)param);
    }

    protected override void OnPropertyChanged(PropertyChangedEventArgs e)
    {
        if (SynchronizationContext.Current == _synchronizationContext)
        {
            // Execute the PropertyChanged event on the current thread
            RaisePropertyChanged(e);
        }
        else
        {
            // Raises the PropertyChanged event on the creator thread
            _synchronizationContext.Send(RaisePropertyChanged, e);
        }
    }

    private void RaisePropertyChanged(object param)
    {
        // We are in the creator thread, call the base implementation directly
        base.OnPropertyChanged((PropertyChangedEventArgs)param);
    }
}
Share:
107,806

Related videos on Youtube

Anindya
Author by

Anindya

I am a software developer from Bangalore,India.I am B.E.(Computer Sc) Tech skills : C# (Winform), WPF(MVVM), SQL

Updated on February 18, 2020

Comments

  • Anindya
    Anindya over 4 years

    I have a DataGrid which is populating data from ViewModel by asynchronous method.My DataGrid is :

    <DataGrid ItemsSource="{Binding MatchObsCollection}"  x:Name="dataGridParent" 
                          Style="{StaticResource EfesDataGridStyle}" 
                          HorizontalGridLinesBrush="#DADADA" VerticalGridLinesBrush="#DADADA" Cursor="Hand" AutoGenerateColumns="False" 
                          RowDetailsVisibilityMode="Visible"  >
    

    I am using http://www.amazedsaint.com/2010/10/asynchronous-delegate-command-for-your.html to implement asynchronous way in my viewmodel.

    Here is my viewmodel code:

    public class MainWindowViewModel:WorkspaceViewModel,INotifyCollectionChanged
        {        
    
            MatchBLL matchBLL = new MatchBLL();
            EfesBetServiceReference.EfesBetClient proxy = new EfesBetClient();
    
            public ICommand DoSomethingCommand { get; set; }
            public MainWindowViewModel()
            {
                DoSomethingCommand = new AsyncDelegateCommand(
                    () => Load(), null, null,
                    (ex) => Debug.WriteLine(ex.Message));           
                _matchObsCollection = new ObservableCollection<EfesBet.DataContract.GetMatchDetailsDC>();                
    
            }       
    
            List<EfesBet.DataContract.GetMatchDetailsDC> matchList;
            ObservableCollection<EfesBet.DataContract.GetMatchDetailsDC> _matchObsCollection;
    
            public ObservableCollection<EfesBet.DataContract.GetMatchDetailsDC> MatchObsCollection
            {
                get { return _matchObsCollection; }
                set
                {
                    _matchObsCollection = value;
                    OnPropertyChanged("MatchObsCollection");
                }
            }        
            //
            public void Load()
            {            
                matchList = new List<GetMatchDetailsDC>();
                matchList = proxy.GetMatch().ToList();
    
                foreach (EfesBet.DataContract.GetMatchDetailsDC match in matchList)
                {
                    _matchObsCollection.Add(match);
                }
    
            }
    

    As you can see in my Load() method in my ViewModel first I am getting matchList (which is a list of a DataContract Class) from my Service.Then by foreach loop I am inserting my matchList items to my _matchObsCollection(which is an ObservableCollection of DataContract Class)).Now here I am getting the above error (as I shown in Title) "This type of CollectionView does not support changes to its SourceCollection from a thread different from the Dispatcher thread" enter image description here

    Can anyone suggest me any solution.Moreover if possible I would like to know how to bind my DataGrid in View and also refresh it asynchronously if any better way is there.

    • Anthony
      Anthony about 10 years
    • Anindya
      Anindya about 10 years
      @Anthony thanks for the post in CodeProject..
    • Anthony
      Anthony about 10 years
      No problem, I'm sure it will work for you. Please leave some feedback at the CodeProject when you get a chance. Good luck!
    • Peter Duniho
      Peter Duniho about 4 years
      See this answer in marked duplicate for the most modern approach as of today.
  • Anindya
    Anindya almost 11 years
    Thanks for prompt response. I have tried uiContext.Send(() => _matchObsCollection.Add(match), null); but it is giving syntax error. Look like Send method is expecting some callback method name. I have tried various options like delegate with parameter but it is not allowing to have my custom parameter. I am very new to this kind of syntax, so it would really helpful if you can provide me syntax to call this?
  • Daniel
    Daniel almost 11 years
    Sorry. I made a mistake and I corrected it. It should be uiContext.Send(x => _matchObsCollection.Add(match), null);
  • Anindya
    Anindya over 10 years
    Thanks @Rohit Vats for your suggestion. I have implemented in that way and it is working .Application.Current.Dispatcher.BeginInvoke((Action)(() => { matchList = new List<GetMatchDetailsDC>(); matchList = proxy.GetMatch().ToList(); foreach (EfesBet.DataContract.GetMatchDetailsDC match in matchList) { _matchObsCollection.Add(match); } }));
  • Rohit Vats
    Rohit Vats over 10 years
    BeginInvoke will update the collection asynchronously. So, if you want it that way then its fine. Hope it helped.
  • Anthony
    Anthony about 10 years
    Or you can use this : codeproject.com/Articles/64936/…
  • Black Dynamite
    Black Dynamite over 8 years
    What on earth is EfesBet? I haven't been able to find ANYTHING mentioning it other than Stackoverflow posts.
  • juFo
    juFo over 8 years
    EfesBet seems to be a service from Anindya. You can make it FooBarService or whatever you call it.
  • Chris Staley
    Chris Staley over 7 years
    I like this one for its simplicity and MVVM-friendliness
  • Puppetmupp
    Puppetmupp over 7 years
    TaskScheduler.FromCurrentSynchronizationContext() is the simplest and most logical solution. Thanks!
  • Tom
    Tom about 7 years
    @juFo - you defined the _lock object as static. I.e. the same lock object for all the collection instances. Any reason to do that?
  • juFo
    juFo about 7 years
    becuase I want to make sure it is created before I call the EnableCollectionSynchronization. But I'm listening if there is another good reason to do it like this (or not).
  • Tom
    Tom about 7 years
    If you remove the "static" keyword it'll still be created before the EnableCollectionSynchronization. A reason to avoid it would be that you're sharing the same lock which could cause a performance hit if you have a lot of collections that change a lot
  • Greg Trevellick
    Greg Trevellick about 6 years
    This worked for me perfectly ! Be sure to look in the comments of Thomas Levesque's website article, as it refers to an updated github gist version of the code that appears at the start of the post.
  • Matt Allen
    Matt Allen about 5 years
    This was a great tip. Thanks.
  • Deantwo
    Deantwo almost 5 years
    Seems like thomaslevesque.com is down or gone. Was a nice solution though, I still have a version of it in my project.
  • Deantwo
    Deantwo almost 5 years
    This information alone doesn't isn't very useful for this issue.
  • Dave Friedel
    Dave Friedel almost 5 years
    I love this but there is an issue around working with the items in the collection as they are added. Since the raise is delayed, I have run into issue getting an accurate count and items not being found in comparison routines during the iteration. Has anyone else seen this? If just for displaying items - it works great.
  • Leniaal
    Leniaal over 4 years
    @DaveFriedel Yes, I'm running into the same issue. Very unreliable solution, suggest steering clear of this one.
  • AriesConnolly
    AriesConnolly about 4 years
    Great solution, thank you.
  • Franck E
    Franck E about 4 years
    Option 3 is great, thank you
  • Plagon
    Plagon almost 4 years
    Although this solution works most of the time, i still encounter situations where the exception occurs.
  • dOxxx
    dOxxx over 3 years
    Thanks for this answer. I was using Dispatcher.Current.Invoke, which was giving me trouble, but using App.Current.Dispatcher worked. Apparently those are different!
  • Mgamerz
    Mgamerz over 3 years
    Haven't exactly determined if it's .NET 5, but noticed after going to .NET 5 the bindingoperations one no longer seems to be working, getting the exceptions again, where as I don't recall seeing them on 3.1 after adding it to my extended class.
  • Arrow_Raider
    Arrow_Raider about 3 years
    I get this nonsense error. No idea what it is talking about. System.Windows.Markup.XamlParseException: 'Must create DependencySource on same Thread as the DependencyObject.'
  • Drew Noakes
    Drew Noakes almost 3 years
    The invoke should be outside the foreach. If you are adding hundreds of items, you will saturate the dispatcher queue and introduce UI delays for the user.