it-swarm.com.ru

Этот тип CollectionView не поддерживает изменения в его SourceCollection из потока, отличного от потока Dispatcher

У меня есть 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 для реализации асинхронного способа в моей модели представления.

Вот мой код модели представления:

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) из моего Service.Then с помощью цикла foreach я вставляю свои элементы matchList в мой _matchObsCollection (который является ObservableCollection класса DataContract)). Теперь здесь я получаю вышеуказанную ошибку (как я показал в заголовке): «Этот тип CollectionView не поддерживает изменения в его SourceCollection из потока, отличного от потока Dispatcher» enter image description here

Может кто-нибудь предложить мне какое-нибудь решение. Более того, если возможно, я хотел бы знать, как связать мою DataGrid в View, а также обновить ее асинхронно, если есть какой-либо лучший способ.

111
Anindya Chatterjee

Поскольку ваш 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);
            });
        }
    }
194
Rohit Vats

Если я не ошибаюсь, в 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

51
Daniel

Вы можете сделать это:

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, чтобы легко отлаживать и видеть, в каких потоках вы находитесь.

41
juFo

Однажды я столкнулся с той же проблемой и решил проблему с помощью AsyncObservableCollection ( http://www.thomaslevesque.com/2009/04/17/wpf-binding-to-an-asynchronous-collection/ ).

3
mnyarar

Если вы используете BackgroundWorker, вы должны вызвать событие в том же потоке пользовательского интерфейса.

Например, если у вас есть два вида A и B и следующий код внутри A вызывает событие 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
    }

Метод WorkerDoWork выполняется в потоке, который отличается от пользовательского интерфейса.

2
Gianluca Conte

В моем случае (я заполняю 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());
2
Vladislav

Я нашел решение здесь: https://www.thomaslevesque.com/2009/04/17/wpf-binding-to-an-asynchronous-collection/ Вы просто создаете новый класс и использовать его вместо ObservableCollection. Это сработало для меня.

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);
    }
}
1
Istvan Heckl

Я также получил эту ошибку:

«Этот тип CollectionView не поддерживает изменения в его SourceCollection из потока, отличного от потока Dispatcher»

Оказывается, я создал новую конфигурацию под названием «Release Android», которая была копией конфигурации «Release», и использовал ее для создания новой версии в Менеджере архивов. Я перешел обратно в конфигурацию «Релиз» и все построено нормально. Нет больше ошибок.

Надеюсь, это кому-нибудь поможет.

0
Shane