在大型数据绑定ObservableCollection中添加/删除许多项而不冻结GUI

时间:2012-07-23 14:19:42

标签: c# wpf infragistics

我和我的团队正在开发一个WPF应用程序,它显示了几个并发的XamDataChart控件(由Infragistics提供)。每个图表都绑定到一个不同的ObservableCollection,最多可包含200万个点。对于每个图表,DispatcherTimer会定期检索要附加到集合的新项目(每100毫秒最多1000个)。每次新项目到来时,它们都会被添加到集合的“尾部”,并且从“head”中删除相同的金额,这样集合中的项目数量会随着时间的推移保持不变。

我们面临的问题是添加/删除操作会冻结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);                                                                                              }));
}

提前致谢, 詹卢卡

2 个答案:

答案 0 :(得分:1)

在不了解更多关于您的方案的情况下很难知道正确的建议,因此我建议您将样本发布到Infragistics论坛并寻求帮助,如果您还没有这样做,如果您有,请回复这个帖子的链接,我会看看它。

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

如果您只有两个系列,并且每100毫秒只更新一次,我不认为这会导致UI冻结,但如果您有许多单独的系列,您将使用单独的调度程序交互单独更新数据实际上会比你想要的更多地刷新图表。

图表将批量修改并限制刷新次数,但它使用调度程序执行此操作,因此如果您有15个不同的系列,您将在不同的时间间隔更新,并且所有系列都作为单独的调度程序thunks,那么您将与通过更新同一个调度程序thunk中的多个系列数据源来限制更新次数相比,会导致对图表进行更多更新。

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

如果您的数据项支持INotifyPropertyChanged,但您没有使用它来通知图表的值更改,那么使用不实现INotifyPropertyChanged的项类型会好得多。如果您提交实现此接口的项目,则图表会假定它需要订阅此项以便收到更改通知(您可能永远不会打算这样做)。这可能听起来不像是一个问题,但如果你有200万条记录,你正在以高频率进行更新,那么很多事件订阅都是无意义的。

据我所知,图表从属性绑定中检索值比使用字符串索引器快得多,因此请确保它是一个简单的属性,而不是成员路径中的虚线属性路径。

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

答案 1 :(得分:0)

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

我最终做的是实际使用两种方法:一个后台线程来获取数据,ObservableCollection将项目添加到DispatcherPriority.Background Task<List<MyClass>> task = Task.Factory.StartNew(() => GetData()); App.Current.Dispatcher.BeginInvoke(DispatcherPriority.Background, new Action(delegate() { MyObservableCollection.AddRange(task.Result); })); 优先级。< / p>

我的代码通常如下所示:

await

我过去使用过的另一个选项是来自Async CTP Refreshasync / async void SomeMethod() { Task<List<MyClass>> task = Task.Factory.StartNew(() => GetData()); MyObservableCollection.AddRange(await task); } 个关键字

AddRange()

注意ObservableCollection方法是扩展public void AddRange(IEnumerable<T> collection) { foreach (var i in collection) Items.Add(i); OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset)); } 的自定义类的一部分,尽管您也可以将其构建为Extension方法。

CollectionChanged

您可以为最后添加的每个项目添加{{1}}来电,而不是单个重置调用,但这可能会导致您添加的数据量出现性能问题。