在不冻结GUI的情况下,向大型数据绑定ObservableCollection添加/从中删除许多项



我和我的团队正在开发一个WPF应用程序,该应用程序显示几个并发的XamDataChart控件(由Infrastics提供)。每个图表都绑定到一个不同的ObservableCollection,该集合最多可包含200万个点。对于每个图表,DispatcherTimer会定期检索要附加到集合的新项目(每100毫秒最多1000个)。每次有新的项目出现时,它们都会被添加到集合的"尾部",并从"头部"删除相同的数量,这样集合中的项目数量就可以在一段时间内保持不变。

我们面临的问题是,添加/删除操作正在冻结GUI,因为集合只能由主线程修改。我们已经尝试了许多方法(BackgroundWorker、Application.Current.Dispatcher with DispatcherPriority.Background、Task.Factory等),但似乎都无法解决问题,GUI一直处于冻结状态。

你能告诉我们在保持GUI响应的同时处理大量绑定数据的最佳方法吗?

更新

1) 正如我在下面的评论中所指出的,我们已经尝试在抑制OnCollectionChanged的同时添加和删除项目。即使它似乎对少量数据产生了影响,但在我们的场景中,这种解决方案的优势确实无法观察到。

2) 数据是在单独的线程中准备和收集的。这是一个长时间运行的操作,但没有明显的缓慢或无响应。将数据传递到图表组件进行渲染时,应用程序将冻结。

3) 以下是生成数据(在单独的线程中)并在UI上显示数据的方法:

private void GenerateDataButtonClick(object sender, RoutedEventArgs e)
{
   Task<List<RealTimeDataPoint>> task = Task.Factory.StartNew(() =>           this.RealTimeDataPointGenerator.GenerateData(2000000));
   Application.Current.Dispatcher.BeginInvoke(DispatcherPriority.Background, new Action(() =>  {                                                                                                    this.DataPoints.Clear();
                                                                                            this.DataPoints.AddRange(task.Result);
                                                                                                 if (!this.StartDataFeedButton.IsEnabled)
                                                                                                     this.StartDataFeedButton.IsEnabled = true;
                                                                                             }));
}
public void DispatcherTimerTick(object sender, EventArgs e)
{
   Task<List<RealTimeDataPoint>> task = Task.Factory.StartNew(() => this.RealTimeDataPointGenerator.GenerateData(1000));
   Application.Current.Dispatcher.BeginInvoke(DispatcherPriority.Background, new Action(() => {                                                                                                  this.DataPoints.RemoveRange(0, task.Result.Count);                                                                                                  this.DataPoints.AddRange(task.Result);                                                                                              }));
}

提前感谢,Gianluca

如果不了解更多关于你的场景的信息,很难知道正确的建议,所以我建议你在Infrastics论坛上发布一个样本,并寻求帮助,如果你还没有这样做,如果你已经这样做了,请回复帖子链接,我会看看的。

如果将多个点作为单独的操作同时更新,则使集合只引发一个事件而不是每个单独的事件会很有帮助。例如,如果通过重复调用Add来更新集合的整个内容,则最好发送一个Reset事件,而不是所有单独的事件。但你似乎已经在调用AddRange了,我相信它只会向图表发送一个通知。

如果你只有两个系列,并且每100毫秒只更新一次,我不认为这会导致UI冻结,但如果你有许多单独的系列,你通过单独的调度器交互单独更新数据,你实际上会导致比你预期的更多的图表刷新。

图表将批量修改并限制刷新次数,但它使用调度器来完成这一操作,因此,如果您有15个不同的系列,您将在不同的时间间隔进行更新,所有这些都是单独的调度器thunk,那么与通过在同一个调度器thunck中更新多个系列数据源来限制更新数量相比,您将对图表进行更多的更新。

此外,如果您使用的是CategoryDateTimeXAxis,在上面的代码中,您可能会遇到当前在修改时对日期列进行排序的限制,这将扼杀您在该级别上的性能。在这种情况下,我建议提交该轴类型的功能请求,以支持预先排序的数据。

如果您的数据项支持INotifyPropertyChanged,但您没有使用它来通知图表值的更改,那么最好使用不实现INotifyAttributeChanged的项类型。如果您提交了实现此接口的项目,图表会认为它需要订阅此项才能收到更改通知(您可能从未打算进行更改)。这听起来可能不是问题,但如果你有200万条记录正在高频率更新,那么就会产生大量的事件订阅。

据我所知,该图表在从属性绑定检索值方面比字符串索引器快得多,因此请确保它是一个简单的属性,并且在MemberPath中不是虚线属性路径。

希望这些信息对你有用。但是,使用一个可运行的样本来诊断问题要容易得多,该样本提供了可能导致问题的所有上下文。

我也遇到过这个问题,我的代码获取数据通常需要一段时间,尽管使用了DispatcherPriority.Background,但它在加载时会冻结UI,而且我不能使用后台线程,因为WPF不能修改没有创建对象的线程上的对象。

我最终使用了两种方法:一个是后台线程获取数据,另一个是DispatcherDispatcherPriority.Background的优先级将项目添加到ObservableCollection

我的代码通常如下所示:

Task<List<MyClass>> task = Task.Factory.StartNew(() => GetData());
App.Current.Dispatcher.BeginInvoke(DispatcherPriority.Background, 
    new Action(delegate() 
    {
        MyObservableCollection.AddRange(task.Result);
    }));

我过去使用的另一个选项是Async CTP Refresh 中的await/async关键字

async void SomeMethod()
{
    Task<List<MyClass>> task = Task.Factory.StartNew(() => GetData());
    MyObservableCollection.AddRange(await task);
}

注意AddRange()方法是扩展ObservableCollection的自定义类的一部分,尽管您也可以将其构建为Extension方法。

public void AddRange(IEnumerable<T> collection)
{
    foreach (var i in collection)
        Items.Add(i);
    OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
}

您可以为添加的每个项目添加一个CollectionChanged调用,而不是在最后添加一个Reset调用,但这可能会导致添加的数据量出现性能问题。

相关内容

  • 没有找到相关文章

最新更新