如何提高向ListView添加范围的性能

时间:2014-04-30 02:23:23

标签: c# .net wpf performance listview

我的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,还是这种正常的性能?如何让这段代码比目前运行得更快?

3 个答案:

答案 0 :(得分:0)

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

答案 1 :(得分:0)

我的问题是,确实,我认为继承可枚举是一个非常糟糕的主意,而我应该只使用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毫秒。

答案 2 :(得分:0)

您可以尝试的一件事可能是使用局部变量进行存储,然后将其分配给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();
}