This type of CollectionView does not support changes to its SourceCollection from a thread different from the Dispatcher thread
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);
}
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);
}
}
Related videos on Youtube
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, 2020Comments
-
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"
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 about 10 yearsSolution here : codeproject.com/Articles/64936/…
-
Anindya about 10 years@Anthony thanks for the post in CodeProject..
-
Anthony about 10 yearsNo 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 about 4 yearsSee this answer in marked duplicate for the most modern approach as of today.
-
-
Anindya almost 11 yearsThanks 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 almost 11 yearsSorry. I made a mistake and I corrected it. It should be uiContext.Send(x => _matchObsCollection.Add(match), null);
-
Anindya over 10 yearsThanks @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 over 10 years
BeginInvoke
will update the collection asynchronously. So, if you want it that way then its fine. Hope it helped. -
Anthony about 10 yearsOr you can use this : codeproject.com/Articles/64936/…
-
Black Dynamite over 8 yearsWhat on earth is EfesBet? I haven't been able to find ANYTHING mentioning it other than Stackoverflow posts.
-
juFo over 8 yearsEfesBet seems to be a service from Anindya. You can make it FooBarService or whatever you call it.
-
Chris Staley over 7 yearsI like this one for its simplicity and MVVM-friendliness
-
Puppetmupp over 7 years
TaskScheduler.FromCurrentSynchronizationContext()
is the simplest and most logical solution. Thanks! -
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 about 7 yearsbecuase 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 about 7 yearsIf 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 about 6 yearsThis 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 about 5 yearsThis was a great tip. Thanks.
-
Deantwo almost 5 yearsSeems like thomaslevesque.com is down or gone. Was a nice solution though, I still have a version of it in my project.
-
Deantwo almost 5 yearsThis information alone doesn't isn't very useful for this issue.
-
Dave Friedel almost 5 yearsI 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 over 4 years@DaveFriedel Yes, I'm running into the same issue. Very unreliable solution, suggest steering clear of this one.
-
AriesConnolly about 4 yearsGreat solution, thank you.
-
Franck E about 4 yearsOption 3 is great, thank you
-
Plagon almost 4 yearsAlthough this solution works most of the time, i still encounter situations where the exception occurs.
-
dOxxx over 3 yearsThanks 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 over 3 yearsHaven'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 about 3 yearsI 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 almost 3 yearsThe 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.