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

Ani*_*jee 129 c# silverlight wpf xaml mvvm

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

<DataGrid ItemsSource="{Binding MatchObsCollection}"  x:Name="dataGridParent" 
                      Style="{StaticResource EfesDataGridStyle}" 
                      HorizontalGridLinesBrush="#DADADA" VerticalGridLinesBrush="#DADADA" Cursor="Hand" AutoGenerateColumns="False" 
                      RowDetailsVisibilityMode="Visible"  >
Run Code Online (Sandbox Code Playgroud)

我正在使用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);
            }

        }
Run Code Online (Sandbox Code Playgroud)

正如你可以在我的Load()方法看到我的ViewModel第一我越来越matchList(这是一个DataContract类的列表)从我Service.Then通过foreach循环,我将我的matchList项目,我的_matchObsCollection(这是一个ObservableCollection DataContract类)).现在,我在这里得到上面的错误(如标题所示我)"这种类型的CollectionView不支持从一个线程从调度线程不同其SourceCollection变化"的 在此输入图像描述

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

Roh*_*ats 217

由于您的ObservableCollection是在UI线程上创建的,因此您只能从UI线程而不是从其他线程修改它.这被称为线程亲和力.

如果您需要更新从不同线程在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);
            });
        }
    }
Run Code Online (Sandbox Code Playgroud)

  • `BeginInvoke`将异步更新集合.所以,如果你想那样那么它就好了.希望它有所帮助. (2认同)

Dan*_*iel 63

如果我没记错,在WPF 4.5中,您应该能够毫无问题地执行此操作.

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

var uiContext = SynchronizationContext.Current;
Run Code Online (Sandbox Code Playgroud)

然后你在你的线程中使用它:

uiContext.Send(x => _matchObsCollection.Add(match), null);
Run Code Online (Sandbox Code Playgroud)

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


juF*_*uFo 46

你可以这样做:

App.Current.Dispatcher.Invoke((System.Action)delegate
             {
               _matchObsCollection.Add(match)
             });
Run Code Online (Sandbox Code Playgroud)

对于.NET 4.5+:您可以按照Daniel的回答.在他的示例中,您要向发布者提供他们需要在正确的线程上调用或调用的责任:

var uiContext = SynchronizationContext.Current;
uiContext.Send(x => _matchObsCollection.Add(match), null);
Run Code Online (Sandbox Code Playgroud)

或者您可以将责任放在您的service/viewmodel/whatever上,并简单地启用CollectionSynchronization.这样,如果您拨打电话,则无需担心您所在的线路以及您拨打电话的线路.责任不再适用于发布者. (这可能会给您带来一些性能开销,但在中央服务中执行此操作,它可以为您节省大量异常并为您提供更轻松的应用程序维护.)

private static object _lock = new object();

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

更多信息:https://msdn.microsoft.com/en-us/library/system.windows.data.bindingoperations.enablecollectionsynchronization(v=vs.110).aspx

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

  • 如果删除"static"关键字,它仍将在EnableCollectionSynchronization之前创建.避免它的一个原因是你共享同一个锁,如果你有很多变化很多的集合,可能会导致性能下降 (3认同)
  • EfesBet 似乎是 Anindya 的一项服务。你可以让它成为 FooBarService 或任何你称之为的东西。 (2认同)

mny*_*rar 6

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

  • 这完全适合我!一定要查看Thomas Levesque的网站文章的评论,因为它是指在文章开头显示的代码的更新的github gist版本。 (2认同)

Ist*_*ckl 6

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

  • @DaveFriedel 是的,我遇到了同样的问题。非常不可靠的解决方案,建议避开这个。 (3认同)