Этот тип CollectionView не поддерживает изменения в его SourceCollection из потока, отличного от потока диспетчера
у меня есть DataGrid, который заполняет данные из ViewModel асинхронным способом method.My DataGrid - это:
<DataGrid ItemsSource="{Binding MatchObsCollection}" x:Name="dataGridParent"
Style="{StaticResource EfesDataGridStyle}"
HorizontalGridLinesBrush="#DADADA" VerticalGridLinesBrush="#DADADA" Cursor="Hand" AutoGenerateColumns="False"
RowDetailsVisibilityMode="Visible" >
Я использую http://www.amazedsaint.com/2010/10/asynchronous-delegate-command-for-your.html реализовать асинхронный способ в моем viewmodel.
вот мой код viewmodel:
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);
}
}
Как вы можете видеть в моем методе Load() в моем ViewModel сначала я получаю matchList (который является списком класса DataContract) из моего Услуга.Затем по циклу foreach я вставляю свои элементы matchList в свою _matchObsCollection (которая является ObservableCollection класса DataContract)).Теперь здесь я получаю вышеуказанную ошибку (как я показал в заголовке) "этот тип CollectionView не поддерживает изменения в его SourceCollection из потока, отличного от потока диспетчера"

может ли кто-нибудь предложить мне какое-либо решение.Кроме того, если это возможно, я хотел бы знать, как привязать мой DataGrid в поле зрения, а также обновить его асинхронно, если есть лучший способ.
6 ответов:
поскольку ваша ObservableCollection создается в потоке пользовательского интерфейса, вы можете изменить его только из потока пользовательского интерфейса, а не из других потоков. Это называется потоку.
Если вам когда-нибудь понадобится обновить объекты, созданные в потоке пользовательского интерфейса из другого потока, просто
put the delegate on UI Dispatcherи это будет работать для вас делегирования его в поток пользовательского интерфейса. Это будет работать -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); }); } }
Если я не ошибаюсь, в WPF 4.5, вы должны быть в состоянии сделать это без каких-либо проблем.
теперь, чтобы решить эту проблему, вы должны использовать контекст синхронизации. Перед запуском потока необходимо сохранить контекст синхронизации в потоке пользовательского интерфейса.
var uiContext = SynchronizationContext.Current;затем вы используете его в своем потоке:
uiContext.Send(x => _matchObsCollection.Add(match), null);взгляните на этот туто http://www.codeproject.com/Articles/31971/Understanding-SynchronizationContext-Part-I
вы можете сделать это:
App.Current.Dispatcher.Invoke((System.Action)delegate { _matchObsCollection.Add(match) });для .NET 4.5+: вы можете следить за ответом Даниила. В его примере вы даете ответственность издателю, который им нужно вызвать или вызвать в правильном потоке:
var uiContext = SynchronizationContext.Current; uiContext.Send(x => _matchObsCollection.Add(match), null);или вы могли бы поставить ответственность за свой сервис / viewmodel / что угодно и просто включить CollectionSynchronization. Таким образом, если вы делаете вызов, вам не нужно беспокоиться о том, на каком потоке вы находитесь и на каком из них вы делаете вызов. Ответственность не для издателя. (это может дать вам немного накладных расходов производительности, но делать это в Центральной службе, это может сэкономить вам много исключений и дает вам более легкое обслуживание приложений.)
private static object _lock = new object(); public MainWindowViewModel() { // ... _matchObsCollection = new ObservableCollection<EfesBet.DataContract.GetMatchDetailsDC>(); BindingOperations.EnableCollectionSynchronization(_matchObsCollection , _lock); }дополнительная информация: https://msdn.microsoft.com/en-us/library/system.windows.data.bindingoperations.enablecollectionsynchronization(v=vs.110).aspx
в Visual Studio 2015 (Pro) перейдите к Debug -- > Windows --> Threads чтобы легко отлаживать и видеть, на каких потоках вы находитесь.
Я испытал ту же проблему один раз и решил проблему с AsyncObservableCollection (http://www.thomaslevesque.com/2009/04/17/wpf-binding-to-an-asynchronous-collection/).
в моем случае (я заселился
ObservableCollectionс асинхронными задачами и не имеют доступа кAppпримеру) я используюTaskScheduler.FromCurrentSynchronizationContext()для очистки коллекции по ошибке:// 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());
Если вы используете BackgroundWorker, вы должны вызвать событие в том же потоке пользовательского интерфейса.
для т. е. если у вас есть два представления A и B и следующий код внутри поднимает события пробуждения событие
//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 }метод WorkerDoWork выполняется в потоке, который не совпадает с пользовательским интерфейсом.
Comments