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



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

1208   6  

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

    Ничего не найдено.