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

问题描述 投票:114回答:8

我有一个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在我的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);
            }

        }

正如您在我的ViewModel中的Load()方法中所看到的,我首先从我的Service获取matchList(这是一个DataContract类的列表)。然后通过foreach循环我将我的matchList项插入到我的_matchObsCollection(这是一个ObservableCollection) DataContract类))。现在我在这里得到上面的错误(正如我在标题中所示)“这种类型的CollectionView不支持从不同于Dispatcher线程的线程更改其SourceCollection”

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

c# wpf xaml silverlight mvvm
8个回答
207
投票

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

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

61
投票

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

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

var uiContext = SynchronizationContext.Current;

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

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

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


45
投票

你可以这样做:

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

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

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

或者您可以将责任放在您的service / viewmodel / whatever上,并简单地启用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以轻松调试并查看您所在的线程。


6
投票

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


3
投票

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

2
投票

在我的情况下(我使用异步任务填充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
投票

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

例如,如果您有两个视图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方法在与UI不同的线程中执行。


0
投票

我也得到了这个错误:

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

事实证明我创建了一个名为“Release Android”的新配置,它是“Release”配置的副本,并使用它在Archive Manager中创建新版本。我改回了配置“发布”,一切都很好。没有更多的错误。

希望这有助于某人。

© www.soinside.com 2019 - 2024. All rights reserved.