Этот тип CollectionView не поддерживает изменения в его SourceCollection из потока, отличного от потока диспетчера
У меня есть DataGrid, который заполняет данные из ViewModel асинхронным методом.Мой 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 в my _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);
взгляните на эту tuto 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 / whatever и просто включить 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) перейдите в Отладка --> Windows --> Нити легко отлаживать и видеть, на каких потоках вы находитесь.
Я испытал ту же проблему один раз и решил проблему с AsyncObservableCollection (http://www.thomaslevesque.com/2009/04/17/wpf-binding-to-an-asynchronous-collection/).
в моем случае (я заселился ObservableCollection
с асинхронными задачами и не имеют доступа к App
примеру) я использую TaskScheduler.FromCurrentSynchronizationContext()
для очистки коллекции на 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());
Если вы используете 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 выполняется в потоке, который не совпадает с пользовательским интерфейсом.