167

我有一个 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在我的视图模型中实现异步方式。

这是我的视图模型代码:

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 DataContract 类))。现在我收到上述错误(如标题中所示)“这种类型的 CollectionView 不支持从不同于 Dispatcher 线程的线程更改其 SourceCollection” 在此处输入图像描述

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

4

8 回答 8

266

由于您的 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);
            });
        }
    }
于 2013-08-20T13:32:07.823 回答
71

如果我没记错的话,在 WPF 4.5 中,你应该可以毫无问题地做到这一点。

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

var uiContext = SynchronizationContext.Current;

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

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

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

于 2013-08-20T09:44:10.267 回答
57

你可以这样做:

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

或者您可以将责任放在您的服务/视图模型/任何东西上,然后简单地启用 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 以轻松调试并查看您所在的线程。

于 2015-10-26T10:53:41.647 回答
8

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

于 2014-03-23T12:21:39.447 回答
4

我在这里找到了解决方案: 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);
    }
}
于 2019-02-17T12:49:30.297 回答
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());
于 2014-10-24T08:39:20.307 回答
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 不同的线程中执行。

于 2016-07-20T10:58:59.047 回答
-3

我也收到了这个错误:

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

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

希望这可以帮助某人。

于 2019-02-14T08:51:39.343 回答