Этот тип 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 из потока, отличного от потока диспетчера" enter image description here

может ли кто-нибудь предложить мне какое-либо решение.Кроме того, если возможно, я хотел бы знать, как связать мой 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 выполняется в потоке, который не совпадает с пользовательским интерфейсом.