可以将文章内容翻译成中文,广告屏蔽插件可能会导致该功能失效(如失效,请关闭广告屏蔽插件后再试):
问题:
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.
回答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);
});
}
}
回答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
回答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.
回答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/).
回答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);
}
}
回答6:
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());
回答7:
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.
回答8:
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.