Programing

이 유형의 CollectionView는 Dispatcher 스레드와 다른 스레드에서 SourceCollection의 변경을 지원하지 않습니다.

lottogame 2020. 7. 4. 10:39
반응형

이 유형의 CollectionView는 Dispatcher 스레드와 다른 스레드에서 SourceCollection의 변경을 지원하지 않습니다.


비동기 메소드에 의해 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);
            }

        }

내 ViewModel의 내 Load () 메서드에서 볼 수 있듯이 먼저 서비스에서 matchList (DataContract 클래스 목록)를 가져옵니다 .foreach 루프로 matchList 항목을 _matchObsCollection (ObservableCollection)에 삽입합니다 이제 제목에 표시된대로 위의 오류가 발생합니다. "이 유형의 CollectionView는 Dispatcher 스레드와 다른 스레드에서 SourceCollection의 변경을 지원하지 않습니다." 여기에 이미지 설명을 입력하십시오

누구나 나에게 어떤 솔루션을 제안 할 수 있습니까? 가능한 경우 View에서 내 DataGrid를 바인딩하고 더 나은 방법이 있으면 비동기식으로 새로 고치는 방법을 알고 싶습니다.


ObservableCollection은 UI 스레드에서 작성되므로 다른 스레드가 아닌 UI 스레드에서만 수정할 수 있습니다. 이것을 스레드 선호도 라고 합니다.

If you ever need to update objects created on UI thread from different thread, simply put the delegate on UI Dispatcher and that will do work for you delegating it to UI thread. This will work -

    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);
            });
        }
    }

If I'm not mistaken, in WPF 4.5, you should be able to do this without any problem.

Now to solve this, you should use the synchronization context. Before you launch the thread, you have to store the synchronization context in the ui thread.

var uiContext = SynchronizationContext.Current;

Then you use it in your thread:

uiContext.Send(x => _matchObsCollection.Add(match), null);

Take a look at this tuto http://www.codeproject.com/Articles/31971/Understanding-SynchronizationContext-Part-I


You can do this:

App.Current.Dispatcher.Invoke((System.Action)delegate
             {
               _matchObsCollection.Add(match)
             });

For .NET 4.5+: You can follow the answer of Daniel. In his example you give the responsability to the publisher that they need to call or invoke on the correct thread:

var uiContext = SynchronizationContext.Current;
uiContext.Send(x => _matchObsCollection.Add(match), null);

Or you could put the responsability to your service/viewmodel/whatever and simply enable CollectionSynchronization. This way if you make a call you don't have to worry on which thread you are on and on which one you make the call. The responsability is not for the Publisher anymore. (This may give you a little performance overhead but doing this in a central service, it can save you a lot of exceptions and gives you easier application maintenance.)

private static object _lock = new object();

public MainWindowViewModel()
{
    // ...
    _matchObsCollection = new ObservableCollection<EfesBet.DataContract.GetMatchDetailsDC>();
    BindingOperations.EnableCollectionSynchronization(_matchObsCollection , _lock);
} 

More info: https://msdn.microsoft.com/en-us/library/system.windows.data.bindingoperations.enablecollectionsynchronization(v=vs.110).aspx

In Visual Studio 2015 (Pro) go to Debug --> Windows --> Threads to easily debug and see on which threads you are on.


I have experienced the same issue once and resolved the issue with AsyncObservableCollection (http://www.thomaslevesque.com/2009/04/17/wpf-binding-to-an-asynchronous-collection/).


I have found a solution here: https://www.thomaslevesque.com/2009/04/17/wpf-binding-to-an-asynchronous-collection/ You just create a new class and use it instead of ObservableCollection. It worked for me.

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);
    }
}

In my case (I populate ObservableCollection with asynchronous tasks and do not have access to App instance) I use TaskScheduler.FromCurrentSynchronizationContext() to cleanup the collection on 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());

If you are using BackgroundWorker you should raise the event in the same thread of the UI.

For i.e. if you have two views A and B and the following code inside A raises the event 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 메서드는 UI와 동일하지 않은 스레드에서 실행됩니다.


이 오류도 발생했습니다.

"이 유형의 CollectionView는 디스패처 스레드와 다른 스레드에서 SourceCollection의 변경을 지원하지 않습니다."

"Release"구성의 사본 인 "Release Android"라는 새 구성을 작성했으며이를 사용하여 Archive Manager에서 새 릴리스를 작성했습니다. "릴리스"구성으로 다시 변경하고 모든 것이 제대로 구축되었습니다. 더 이상 오류가 없습니다.

이것이 누군가를 돕기를 바랍니다.

참고 URL : https://stackoverflow.com/questions/18331723/this-type-of-collectionview-does-not-support-changes-to-its-sourcecollection-fro

반응형