C# 这种类型的 CollectionView 不支持从不同于 Dispatcher 线程的线程更改其 SourceCollection

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/18331723/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-10 11:56:47  来源:igfitidea点击:

This type of CollectionView does not support changes to its SourceCollection from a thread different from the Dispatcher thread

c#wpfxamlsilverlightmvvm

提问by Anindya

I have a DataGrid which is populating data from ViewModel by asynchronous method.My DataGrid is :

我有一个 DataGrid,它通过异步方法从 ViewModel 填充数据。我的 DataGrid 是:

<DataGrid ItemsSource="{Binding MatchObsCollection}"  x:Name="dataGridParent" 
                      Style="{StaticResource EfesDataGridStyle}" 
                      HorizontalGridLinesBrush="#DADADA" VerticalGridLinesBrush="#DADADA" Cursor="Hand" AutoGenerateColumns="False" 
                      RowDetailsVisibilityMode="Visible"  >

I am using http://www.amazedsaint.com/2010/10/asynchronous-delegate-command-for-your.htmlto implement asynchronous way in my viewmodel.

我正在使用http://www.amazedsaint.com/2010/10/asynchronous-delegate-command-for-your.html在我的视图模型中实现异步方式。

Here is my viewmodel code:

这是我的视图模型代码:

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

        }

As you can see in my Load() method in my ViewModel first I am getting matchList (which is a list of a DataContract Class) from my Service.Then by foreach loop I am inserting my matchList items to my _matchObsCollection(which is an ObservableCollection of DataContract Class)).Now here I am getting the above error (as I shown in Title) "This type of CollectionView does not support changes to its SourceCollection from a thread different from the Dispatcher thread" enter image description here

正如您在 ViewModel 的 Load() 方法中看到的,首先我从我的服务中获取 matchList(这是一个 DataContract 类的列表)。然后通过 foreach 循环我将我的 matchList 项目插入到我的 _matchObsCollection(这是一个 ObservableCollection DataContract 类)))。现在我收到上述错误(如标题所示)“这种类型的 CollectionView 不支持从不同于调度程序线程的线程更改其 SourceCollection” 在此处输入图片说明

Can anyone suggest me any solution.Moreover if possible I would like to know how to bind my DataGrid in View and also refresh it asynchronously if any better way is there.

任何人都可以向我建议任何解决方案。此外,如果可能的话,我想知道如何在 View 中绑定我的 DataGrid 并在有更好的方法时异步刷新它。

采纳答案by Rohit Vats

Since your ObservableCollection is created on UI thread, you can only modify it from UI thread and not from other threads. This is termed as thread affinity.

由于您的 ObservableCollection 是在 UI 线程上创建的,因此您只能从 UI 线程而不是从其他线程修改它。这称为线程关联

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

如果您需要从不同的线程更新在 UI 线程上创建的对象,那么简单地put the delegate on UI Dispatcher将其委托给 UI 线程即可。这将工作 -

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

回答by Daniel

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

如果我没记错的话,在 WPF 4.5 中,您应该可以毫无问题地执行此操作。

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.

现在要解决这个问题,您应该使用同步上下文。在启动线程之前,您必须将同步上下文存储在 ui 线程中。

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

看看这个教程 http://www.codeproject.com/Articles/31971/Understanding-SynchronizationContext-Part-I

回答by mnyarar

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/).

我曾经遇到过同样的问题,并通过 AsyncObservableCollection ( http://www.thomaslevesque.com/2009/04/17/wpf-binding-to-an-asynchronous-collection/)解决了这个问题。

回答by Vladislav

In my case (I populate ObservableCollectionwith asynchronous tasks and do not have access to Appinstance) I use TaskScheduler.FromCurrentSynchronizationContext()to cleanup the collection on faulted:

在我的情况下(我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());

回答by juFo

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:

对于 .NET 4.5+:您可以按照 Daniel 的回答进行操作。在他的示例中,您将责任赋予发布者,他们需要在正确的线程上调用或调用:

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.)

或者你可以把责任放在你的服务/视图模型/任何东西上,然后简单地启用 CollectionSynchronization。这样,如果您拨打电话,您就不必担心您在哪个线程上以及在哪个线程上拨打电话。责任不再由出版商承担。 (这可能会给您带来一点性能开销,但在中央服务中执行此操作,可以为您节省大量异常并让您更轻松地维护应用程序。)

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

更多信息: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 --> Threadsto easily debug and see on which threads you are on.

在 Visual Studio 2015 (Pro) 中,转到Debug --> Windows --> Threads以轻松调试并查看您所在的线程。

回答by Gianluca Conte

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

如果您使用的是 BackgroundWorker,则应该在 UI 的同一线程中引发事件

For i.e. if you have two views A and B and the following code inside A raises the event WakeUpEvent

例如,如果您有两个视图 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
    }

The WorkerDoWork method is executed in a thread that is not the same of the UI.

WorkerDoWork 方法在与 UI 不同的线程中执行。

回答by Shane

I was getting this error as well:

我也收到此错误:

"This type of CollectionView does not support changes to its SourceCollection from a thread different from the Dispatcher thread"

“这种类型的 CollectionView 不支持从不同于 Dispatcher 线程的线程更改其 SourceCollection”

Turns out I had created a new configuration named "Release Android" which was a copy of the "Release" configuration and was using that to create the new release in the Archive Manager. I changed back to the configuration "Release" and everything built fine. No more error.

结果我创建了一个名为“Release Android”的新配置,它是“Release”配置的副本,并使用它在存档管理器中创建新版本。我改回配置“发布”,一切都很好。没有更多的错误。

Hope this helps someone.

希望这可以帮助某人。

回答by Istvan Heckl

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.

我在这里找到了一个解决方案: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);
    }
}