181

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.

wonea
  • 4,783
  • 17
  • 86
  • 139
Anindya
  • 2,616
  • 2
  • 21
  • 25

8 Answers8

284

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);
            });
        }
    }
Rohit Vats
  • 79,502
  • 12
  • 161
  • 185
  • Thanks @Rohit Vats for your suggestion. I have implemented in that way and it is working .Application.Current.Dispatcher.BeginInvoke((Action)(() => { matchList = new List(); matchList = proxy.GetMatch().ToList(); foreach (EfesBet.DataContract.GetMatchDetailsDC match in matchList) { _matchObsCollection.Add(match); } })); – Anindya Aug 25 '13 at 13:22
  • 3
    `BeginInvoke` will update the collection asynchronously. So, if you want it that way then its fine. Hope it helped. – Rohit Vats Aug 25 '13 at 13:31
  • 1
    Or you can use this : http://www.codeproject.com/Articles/64936/Multithreaded-ObservableImmutableCollection – Anthony Apr 17 '14 at 17:22
  • 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! – dOxxx Dec 02 '20 at 15:31
  • 1
    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. – Drew Noakes Jul 26 '21 at 13:48
75

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

Daniel
  • 9,312
  • 3
  • 48
  • 48
  • 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? – Anindya Aug 20 '13 at 10:44
  • Sorry. I made a mistake and I corrected it. It should be uiContext.Send(x => _matchObsCollection.Add(match), null); – Daniel Aug 20 '13 at 12:21
  • 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.' – Arrow_Raider May 20 '21 at 15:02
64

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 responsibility 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 responsibility 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 responsibility 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.

Stephen Ostermiller
  • 23,933
  • 14
  • 88
  • 109
juFo
  • 17,849
  • 10
  • 105
  • 142
  • 1
    What on earth is EfesBet? I haven't been able to find ANYTHING mentioning it other than Stackoverflow posts. – Black Dynamite Nov 21 '15 at 22:23
  • 2
    EfesBet seems to be a service from Anindya. You can make it FooBarService or whatever you call it. – juFo Nov 23 '15 at 07:44
  • 1
    I like this one for its simplicity and MVVM-friendliness – Chris Staley Sep 16 '16 at 14:42
  • @juFo - you defined the _lock object as static. I.e. the same lock object for all the collection instances. Any reason to do that? – Tom Apr 19 '17 at 13:38
  • 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). – juFo Apr 19 '17 at 13:47
  • 4
    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 – Tom Apr 19 '17 at 14:26
  • This was a great tip. Thanks. – Matt Allen Mar 06 '19 at 02:59
  • Option 3 is great, thank you – Franck E May 14 '20 at 09:27
  • 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. – Mgamerz Dec 08 '20 at 00:05
8

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/).

mnyarar
  • 515
  • 1
  • 6
  • 13
  • 2
    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. – Greg Trevellick Apr 14 '18 at 07:15
  • Seems like http://www.thomaslevesque.com/ is down or gone. Was a nice solution though, I still have a version of it in my project. – Deantwo Jun 07 '19 at 07:36
  • Great solution, thank you. – AriesConnolly May 05 '20 at 23:00
  • Although this solution works most of the time, i still encounter situations where the exception occurs. – Plagon Jun 15 '20 at 12:01
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);
    }
}
Istvan Heckl
  • 864
  • 10
  • 22
  • 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. – Dave Friedel Aug 07 '19 at 17:09
  • 2
    @DaveFriedel Yes, I'm running into the same issue. Very unreliable solution, suggest steering clear of this one. – Leniaal Nov 13 '19 at 18:03
2

In my case (I populate ObservableCollection with asynchronous tasks and do not have access to App instance) I use TaskScheduler.FromCurrentSynchronizationContext() to cleanup the collection on faulted:

        // some main task
        Task loadFileTask = Task.Factory.StartNew(...);

        Task cleanupTask = loadFileTask.ContinueWith(
            (antecedent) => { CleanupFileList(); },
            /* do not cancel this task */
            CancellationToken.None,
            /* run only if faulted main task */
            TaskContinuationOptions.OnlyOnFaulted,
            /* use main SynchronizationContext */
            TaskScheduler.FromCurrentSynchronizationContext());
Vladislav
  • 1,696
  • 27
  • 37
2

If you are using BackgroundWorker you should raise the event in the same thread of the UI.

For i.e. if you have two views A and B and the following code inside A raises the event WakeUpEvent

//Code inside codebehind or viewmodel of A
    var worker = new BackgroundWorker();
    worker.DoWork += WorkerDoWork; //<-- Don't raise the event WakeUpEvent inside this method
    worker.RunWorkerCompleted += workerRunWorkerCompleted; // <-- Raise the event WakeUpEvent inside this method instead
    worker.RunWorkerAsync();

//Code inside codebehind or viewmodel of view B
    public ViewB () {
        WakeUpEvent += UpdateUICallBack;
    }
    private void UpdateUICallBack() {
        //Update here UI element
    }

The WorkerDoWork method is executed in a thread that is not the same of the UI.

wonea
  • 4,783
  • 17
  • 86
  • 139
Gianluca Conte
  • 480
  • 4
  • 8
-3

I was getting this error as well:

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

Turns out I had created a new configuration named "Release Android" which was a copy of the "Release" configuration and was using that to create the new release in the Archive Manager. I changed back to the configuration "Release" and everything built fine. No more error.

Hope this helps someone.

Shane
  • 5
  • 1
  • 5