【问题标题】:How to improve the performance of adding a range to a ListView如何提高向 ListView 添加范围的性能
【发布时间】:2014-04-30 02:23:23
【问题描述】:

我的 WPF 应用程序中有一个简单的数据绑定元素列表,我试图从中获得更多性能。在下面的代码中,我添加了一百万条记录并表示我的项目列表发生了变化。

我有以下 XAML:

<Window x:Class="Log_.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="Log+ Viewer" Height="400" Width="500">
    <Grid Name="MainGrid">
        <Grid.RowDefinitions>
            <RowDefinition Height="*"/>
        </Grid.RowDefinitions>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="*"/>
        </Grid.ColumnDefinitions>
        <TabControl>
            <TabItem Header="Everything">
                <Grid>
                    <Grid.RowDefinitions>
                        <RowDefinition Height="*"/>
                    </Grid.RowDefinitions>
                    <Grid.ColumnDefinitions>
                        <ColumnDefinition Width="*"/>
                    </Grid.ColumnDefinitions>
                    <ListView ItemsSource="{Binding LogRecords}">
                        <ListView.View>
                            <GridView>
                                <GridViewColumn Header="Message" DisplayMemberBinding="{Binding Message}"/>
                                <GridViewColumn Header="Timestamp" DisplayMemberBinding="{Binding Timestamp}"/>
                            </GridView>
                        </ListView.View>
                    </ListView>
                </Grid>
            </TabItem>
        </TabControl>
    </Grid>
</Window>

这是 C# 代码:

using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;

namespace Log_
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {

        public ObservableList<LogRecord> LogRecords { get; set; }

        public MainWindow()
        {
            InitializeComponent();
            LogRecords = new ObservableList<LogRecord>();
            DataContext = this;
            new Thread(() =>
            {
                LogRecord record = new LogRecord();
                record.Message = "Hello, world.";
                record.Timestamp = DateTime.Now;
                List<LogRecord> logRecordList = new List<LogRecord>();
                for (int i = 0; i < 1000000; i++)
                {
                    logRecordList.Add(record);
                }
                Stopwatch timer = new Stopwatch();
                timer.Start();
                Dispatcher.Invoke(() =>
                {
                    LogRecords.AddRange(logRecordList);
                });
                timer.Stop();
                Console.WriteLine("The operation took {0} milliseconds.", timer.ElapsedMilliseconds);
            }).Start();
        }

        public class LogRecord
        {
            public string Message { get; set; }
            public DateTime Timestamp { get; set; }
        }

        public class ObservableList<T> : IEnumerable<T>, INotifyCollectionChanged
        {

            public List<T> UnderlyingList = new List<T>();

            public event NotifyCollectionChangedEventHandler CollectionChanged;

            public void AddRange(IEnumerable<T> list)
            {
                UnderlyingList.AddRange(list);
                OnCollectionChange(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, UnderlyingList));
            }

            protected virtual void OnCollectionChange(NotifyCollectionChangedEventArgs e)
            {
                if (CollectionChanged != null)
                {
                    CollectionChanged(this, e);
                }
            }

            public IEnumerator<T> GetEnumerator()
            {
                return UnderlyingList.GetEnumerator();
            }

            System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
            {
                return UnderlyingList.GetEnumerator();
            }
        }
    }
}

输出是:“操作耗时 4834 毫秒。”

将这些记录添加为一系列记录似乎是一个荒谬的时间。我在这里打破 UI 虚拟化是因为我的项目源像这样继承 IEnumerable 还是这是正常的性能?我怎样才能让这段代码运行得比现在快得多?

【问题讨论】:

    标签: c# .net wpf performance listview


    【解决方案1】:

    如果您有一百万条记录,您可能不想使用所有这些记录来更新 UI。考虑在用户滚动列表时在网格视图中使用分页或加载记录。

    【讨论】:

    • 但是 ListView 没有包含 UI 虚拟化,所以它默认只显示它需要显示的记录,因此,它是否应该已经不处理这种行为,特别是考虑到这样一个事实由于 ObservableList 中的逻辑,我只发信号通知它更新一次......当然,除非继承 IEnumerable 导致它发出多次信号......?
    • 您是否启用了延迟滚动? ScrollViewer.IsDeferredScrollingEnabled="True"
    • 杰克,我找到了解决方案...我认为继承 IEnumerable 确实是个坏主意...我现在将其发布。请检查一下,但请注意它与启用延迟滚动的 ScrollViewer 没有任何关系。
    【解决方案2】:

    我的问题是,确实,我认为继承可枚举是一个非常糟糕的主意,而我应该只使用 ObservableCollection...考虑性能差异:

    using System;
    using System.Collections.Generic;
    using System.Collections.ObjectModel;
    using System.Collections.Specialized;
    using System.Diagnostics;
    using System.Linq;
    using System.Text;
    using System.Threading;
    using System.Threading.Tasks;
    using System.Windows;
    using System.Windows.Controls;
    using System.Windows.Data;
    using System.Windows.Documents;
    using System.Windows.Input;
    using System.Windows.Media;
    using System.Windows.Media.Imaging;
    using System.Windows.Navigation;
    using System.Windows.Shapes;
    
    namespace Log_
    {
        /// <summary>
        /// Interaction logic for MainWindow.xaml
        /// </summary>
        public partial class MainWindow : Window
        {
    
            public ObservableList<LogRecord> LogRecords { get; set; }
    
            public MainWindow()
            {
                InitializeComponent();
                LogRecords = new ObservableList<LogRecord>();
                DataContext = this;
                new Thread(() =>
                {
                    LogRecord record = new LogRecord();
                    record.Message = "Hello, world.";
                    record.Timestamp = DateTime.Now;
                    List<LogRecord> logRecordList = new List<LogRecord>();
                    for (int i = 0; i < 1000000; i++)
                    {
                        logRecordList.Add(record);
                    }
                    Stopwatch timer = new Stopwatch();
                    timer.Start();
                    Dispatcher.Invoke(() =>
                    {
                        LogRecords.AddRange(logRecordList);
                    });
                    timer.Stop();
                    Console.WriteLine("The operation took {0} milliseconds.", timer.ElapsedMilliseconds);
                }).Start();
            }
    
            public class LogRecord
            {
                public string Message { get; set; }
                public DateTime Timestamp { get; set; }
            }
    
            public class ObservableList<T> : ObservableCollection<T>
            {
    
                public override event NotifyCollectionChangedEventHandler CollectionChanged;
    
                public void AddRange(IEnumerable<T> list)
                {
                    foreach (var item in list)
                    {
                        Items.Add(item);
                    }
                    OnCollectionChange(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
                }
    
                protected virtual void OnCollectionChange(NotifyCollectionChangedEventArgs e)
                {
                    if (CollectionChanged != null)
                    {
                        CollectionChanged(this, e);
                    }
                }
            }
        }
    }
    

    该操作耗时 75 毫秒。

    【讨论】:

      【解决方案3】:

      您可以尝试的一件事可能是使用局部变量进行存储,然后将其分配给LogRecords 属性..

      public MainWindow()
      {
          InitializeComponent();
      
          //A local variable
          var logRecords = new ObservableList<LogRecord>();
          DataContext = this;
          new Thread(() =>
          {
              LogRecord record = new LogRecord();
              record.Message = "Hello, world.";
              record.Timestamp = DateTime.Now;
              List<LogRecord> logRecordList = new List<LogRecord>();
              for (int i = 0; i < 1000000; i++)
              {
                  logRecordList.Add(record);
              }
              Stopwatch timer = new Stopwatch();
              timer.Start();
              Dispatcher.Invoke(() =>
              {
                  // Should prevent UI to update itself
                  logRecords .AddRange(logRecordList);
              });
              timer.Stop();
      
              // Assign to actual collection causing UI update
              LogRecords = logRecords ;    
              Console.WriteLine("The operation took {0} milliseconds.", timer.ElapsedMilliseconds);
      
          }).Start();
      }
      

      【讨论】:

      • 是的。 AddRange 应该对局部变量而不是实际集合进行操作。
      猜你喜欢
      • 2018-11-28
      • 1970-01-01
      • 2016-09-14
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-09-07
      • 2021-09-08
      • 1970-01-01
      相关资源
      最近更新 更多